
## Description Instead of detaching from the spawned App process on the device immediately, keep the LLDB client connection open (in autopilot mode) until the App quits or the server connection is lost. This replicates the behavior of Xcode, which also keeps a debugger attached to the App after launching it. ## Tests This change will be covered by all running benchmarks (which are launched via "flutter run"/"flutter drive"), and probably be covered by all tests as well. I also tested the workflow locally -- including cases where the App or Flutter CLI is terminated first. ## Breaking Change I don't believe this should introduce any breaking changes. The LLDB client automatically exits when the app dies or the device is disconnected, so there shouldn't even be any user-visible changes to the behavior of the tool (besides the output of "-v").
272 lines
8.9 KiB
Dart
272 lines
8.9 KiB
Dart
// Copyright 2016 The Chromium 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/cache.dart';
|
|
import 'package:flutter_tools/src/base/context.dart';
|
|
import 'package:flutter_tools/src/base/io.dart';
|
|
import 'package:flutter_tools/src/base/platform.dart';
|
|
import 'package:flutter_tools/src/dart/pub.dart';
|
|
|
|
import 'package:mockito/mockito.dart';
|
|
import 'package:process/process.dart';
|
|
import 'package:quiver/testing/async.dart';
|
|
|
|
import '../../src/common.dart';
|
|
import '../../src/context.dart';
|
|
|
|
void main() {
|
|
setUpAll(() {
|
|
Cache.flutterRoot = getFlutterRoot();
|
|
});
|
|
|
|
testUsingContext('pub get 69', () async {
|
|
String error;
|
|
|
|
final MockProcessManager processMock = context.get<ProcessManager>();
|
|
|
|
FakeAsync().run((FakeAsync time) {
|
|
expect(processMock.lastPubEnvironment, isNull);
|
|
expect(testLogger.statusText, '');
|
|
pubGet(context: PubContext.flutterTests, checkLastModified: false).then((void value) {
|
|
error = 'test completed unexpectedly';
|
|
}, onError: (dynamic thrownError) {
|
|
error = 'test failed unexpectedly: $thrownError';
|
|
});
|
|
time.elapse(const Duration(milliseconds: 500));
|
|
expect(testLogger.statusText,
|
|
'Running "flutter pub get" in /...\n'
|
|
'pub get failed (69) -- attempting retry 1 in 1 second...\n',
|
|
);
|
|
expect(processMock.lastPubEnvironment, contains('flutter_cli:flutter_tests'));
|
|
expect(processMock.lastPubCache, isNull);
|
|
time.elapse(const Duration(milliseconds: 500));
|
|
expect(testLogger.statusText,
|
|
'Running "flutter pub get" in /...\n'
|
|
'pub get failed (69) -- attempting retry 1 in 1 second...\n'
|
|
'pub get failed (69) -- attempting retry 2 in 2 seconds...\n',
|
|
);
|
|
time.elapse(const Duration(seconds: 1));
|
|
expect(testLogger.statusText,
|
|
'Running "flutter pub get" in /...\n'
|
|
'pub get failed (69) -- attempting retry 1 in 1 second...\n'
|
|
'pub get failed (69) -- attempting retry 2 in 2 seconds...\n',
|
|
);
|
|
time.elapse(const Duration(seconds: 100)); // from t=0 to t=100
|
|
expect(testLogger.statusText,
|
|
'Running "flutter pub get" in /...\n'
|
|
'pub get failed (69) -- attempting retry 1 in 1 second...\n'
|
|
'pub get failed (69) -- attempting retry 2 in 2 seconds...\n'
|
|
'pub get failed (69) -- attempting retry 3 in 4 seconds...\n' // at t=1
|
|
'pub get failed (69) -- attempting retry 4 in 8 seconds...\n' // at t=5
|
|
'pub get failed (69) -- attempting retry 5 in 16 seconds...\n' // at t=13
|
|
'pub get failed (69) -- attempting retry 6 in 32 seconds...\n' // at t=29
|
|
'pub get failed (69) -- attempting retry 7 in 64 seconds...\n', // at t=61
|
|
);
|
|
time.elapse(const Duration(seconds: 200)); // from t=0 to t=200
|
|
expect(testLogger.statusText,
|
|
'Running "flutter pub get" in /...\n'
|
|
'pub get failed (69) -- attempting retry 1 in 1 second...\n'
|
|
'pub get failed (69) -- attempting retry 2 in 2 seconds...\n'
|
|
'pub get failed (69) -- attempting retry 3 in 4 seconds...\n'
|
|
'pub get failed (69) -- attempting retry 4 in 8 seconds...\n'
|
|
'pub get failed (69) -- attempting retry 5 in 16 seconds...\n'
|
|
'pub get failed (69) -- attempting retry 6 in 32 seconds...\n'
|
|
'pub get failed (69) -- attempting retry 7 in 64 seconds...\n'
|
|
'pub get failed (69) -- attempting retry 8 in 64 seconds...\n' // at t=39
|
|
'pub get failed (69) -- attempting retry 9 in 64 seconds...\n' // at t=103
|
|
'pub get failed (69) -- attempting retry 10 in 64 seconds...\n', // at t=167
|
|
);
|
|
});
|
|
expect(testLogger.errorText, isEmpty);
|
|
expect(error, isNull);
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => MockProcessManager(69),
|
|
FileSystem: () => MockFileSystem(),
|
|
Platform: () => FakePlatform(
|
|
environment: <String, String>{},
|
|
),
|
|
});
|
|
|
|
testUsingContext('pub cache in root is used', () async {
|
|
String error;
|
|
|
|
final MockProcessManager processMock = context.get<ProcessManager>() as MockProcessManager;
|
|
final MockFileSystem fsMock = context.get<FileSystem>() as MockFileSystem;
|
|
|
|
FakeAsync().run((FakeAsync time) {
|
|
MockDirectory.findCache = true;
|
|
expect(processMock.lastPubEnvironment, isNull);
|
|
expect(processMock.lastPubCache, isNull);
|
|
pubGet(context: PubContext.flutterTests, checkLastModified: false).then((void value) {
|
|
error = 'test completed unexpectedly';
|
|
}, onError: (dynamic thrownError) {
|
|
error = 'test failed unexpectedly: $thrownError';
|
|
});
|
|
time.elapse(const Duration(milliseconds: 500));
|
|
expect(processMock.lastPubCache, equals(fsMock.path.join(Cache.flutterRoot, '.pub-cache')));
|
|
expect(error, isNull);
|
|
});
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => MockProcessManager(69),
|
|
FileSystem: () => MockFileSystem(),
|
|
Platform: () => FakePlatform(
|
|
environment: <String, String>{},
|
|
),
|
|
});
|
|
|
|
testUsingContext('pub cache in environment is used', () async {
|
|
String error;
|
|
|
|
final MockProcessManager processMock = context.get<ProcessManager>();
|
|
|
|
FakeAsync().run((FakeAsync time) {
|
|
MockDirectory.findCache = true;
|
|
expect(processMock.lastPubEnvironment, isNull);
|
|
expect(processMock.lastPubCache, isNull);
|
|
pubGet(context: PubContext.flutterTests, checkLastModified: false).then((void value) {
|
|
error = 'test completed unexpectedly';
|
|
}, onError: (dynamic thrownError) {
|
|
error = 'test failed unexpectedly: $thrownError';
|
|
});
|
|
time.elapse(const Duration(milliseconds: 500));
|
|
expect(processMock.lastPubCache, equals('custom/pub-cache/path'));
|
|
expect(error, isNull);
|
|
});
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => MockProcessManager(69),
|
|
FileSystem: () => MockFileSystem(),
|
|
Platform: () => FakePlatform(
|
|
environment: <String, String>{'PUB_CACHE': 'custom/pub-cache/path'},
|
|
),
|
|
});
|
|
}
|
|
|
|
typedef StartCallback = void Function(List<dynamic> command);
|
|
|
|
class MockProcessManager implements ProcessManager {
|
|
MockProcessManager(this.fakeExitCode);
|
|
|
|
final int fakeExitCode;
|
|
|
|
String lastPubEnvironment;
|
|
String lastPubCache;
|
|
|
|
@override
|
|
Future<Process> start(
|
|
List<dynamic> command, {
|
|
String workingDirectory,
|
|
Map<String, String> environment,
|
|
bool includeParentEnvironment = true,
|
|
bool runInShell = false,
|
|
ProcessStartMode mode = ProcessStartMode.normal,
|
|
}) {
|
|
lastPubEnvironment = environment['PUB_ENVIRONMENT'];
|
|
lastPubCache = environment['PUB_CACHE'];
|
|
return Future<Process>.value(MockProcess(fakeExitCode));
|
|
}
|
|
|
|
@override
|
|
dynamic noSuchMethod(Invocation invocation) => null;
|
|
}
|
|
|
|
class MockProcess implements Process {
|
|
MockProcess(this.fakeExitCode);
|
|
|
|
final int fakeExitCode;
|
|
|
|
@override
|
|
Stream<List<int>> get stdout => MockStream<List<int>>();
|
|
|
|
@override
|
|
Stream<List<int>> get stderr => MockStream<List<int>>();
|
|
|
|
@override
|
|
Future<int> get exitCode => Future<int>.value(fakeExitCode);
|
|
|
|
@override
|
|
dynamic noSuchMethod(Invocation invocation) => null;
|
|
}
|
|
|
|
class MockStream<T> implements Stream<T> {
|
|
@override
|
|
Stream<S> transform<S>(StreamTransformer<T, S> streamTransformer) => MockStream<S>();
|
|
|
|
@override
|
|
Stream<T> where(bool test(T event)) => MockStream<T>();
|
|
|
|
@override
|
|
Stream<S> map<S>(S Function(T) _) => MockStream<S>();
|
|
|
|
@override
|
|
StreamSubscription<T> listen(void onData(T event), { Function onError, void onDone(), bool cancelOnError }) {
|
|
return MockStreamSubscription<T>();
|
|
}
|
|
|
|
@override
|
|
dynamic noSuchMethod(Invocation invocation) => null;
|
|
}
|
|
|
|
class MockStreamSubscription<T> implements StreamSubscription<T> {
|
|
@override
|
|
Future<E> asFuture<E>([ E futureValue ]) => Future<E>.value();
|
|
|
|
@override
|
|
Future<void> cancel() async { }
|
|
|
|
@override
|
|
dynamic noSuchMethod(Invocation invocation) => null;
|
|
}
|
|
|
|
|
|
class MockFileSystem extends ForwardingFileSystem {
|
|
MockFileSystem() : super(MemoryFileSystem());
|
|
|
|
@override
|
|
File file(dynamic path) {
|
|
return MockFile();
|
|
}
|
|
|
|
@override
|
|
Directory directory(dynamic path) {
|
|
return MockDirectory(path);
|
|
}
|
|
}
|
|
|
|
class MockFile implements File {
|
|
@override
|
|
Future<RandomAccessFile> open({ FileMode mode = FileMode.read }) async {
|
|
return MockRandomAccessFile();
|
|
}
|
|
|
|
@override
|
|
bool existsSync() => true;
|
|
|
|
@override
|
|
DateTime lastModifiedSync() => DateTime(0);
|
|
|
|
@override
|
|
dynamic noSuchMethod(Invocation invocation) => null;
|
|
}
|
|
|
|
class MockDirectory implements Directory {
|
|
MockDirectory(this.path);
|
|
|
|
@override
|
|
final String path;
|
|
|
|
static bool findCache = false;
|
|
|
|
@override
|
|
bool existsSync() => findCache && path.endsWith('.pub-cache');
|
|
|
|
@override
|
|
dynamic noSuchMethod(Invocation invocation) => null;
|
|
}
|
|
|
|
class MockRandomAccessFile extends Mock implements RandomAccessFile {}
|