Make "pub get" retry once a second when it fails. (#11882)
This commit is contained in:
parent
42bd30dd3f
commit
2cbc4a0202
@ -103,7 +103,7 @@ class PackagesTestCommand extends FlutterCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Null> runCommand() => pub(<String>['run', 'test']..addAll(argResults.rest));
|
Future<Null> runCommand() => pub(<String>['run', 'test']..addAll(argResults.rest), retry: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PackagesPassthroughCommand extends FlutterCommand {
|
class PackagesPassthroughCommand extends FlutterCommand {
|
||||||
@ -124,5 +124,5 @@ class PackagesPassthroughCommand extends FlutterCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Null> runCommand() => pub(argResults.rest);
|
Future<Null> runCommand() => pub(argResults.rest, retry: false);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import '../base/common.dart';
|
import '../base/common.dart';
|
||||||
import '../base/file_system.dart';
|
import '../base/file_system.dart';
|
||||||
import '../base/logger.dart';
|
import '../base/logger.dart';
|
||||||
@ -55,7 +57,13 @@ Future<Null> pubGet({
|
|||||||
if (offline)
|
if (offline)
|
||||||
args.add('--offline');
|
args.add('--offline');
|
||||||
try {
|
try {
|
||||||
await pub(args, directory: directory, filter: _filterOverrideWarnings, failureMessage: 'pub $command failed');
|
await pub(
|
||||||
|
args,
|
||||||
|
directory: directory,
|
||||||
|
filter: _filterOverrideWarnings,
|
||||||
|
failureMessage: 'pub $command failed',
|
||||||
|
retry: true,
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
status.stop();
|
status.stop();
|
||||||
}
|
}
|
||||||
@ -73,15 +81,29 @@ typedef String MessageFilter(String message);
|
|||||||
Future<Null> pub(List<String> arguments, {
|
Future<Null> pub(List<String> arguments, {
|
||||||
String directory,
|
String directory,
|
||||||
MessageFilter filter,
|
MessageFilter filter,
|
||||||
String failureMessage: 'pub failed'
|
String failureMessage: 'pub failed',
|
||||||
|
@required bool retry,
|
||||||
}) async {
|
}) async {
|
||||||
final List<String> command = <String>[ sdkBinaryName('pub') ]..addAll(arguments);
|
final List<String> command = <String>[ sdkBinaryName('pub') ]..addAll(arguments);
|
||||||
final int code = await runCommandAndStreamOutput(
|
int attempts = 0;
|
||||||
|
int duration = 1;
|
||||||
|
int code;
|
||||||
|
while (true) {
|
||||||
|
attempts += 1;
|
||||||
|
code = await runCommandAndStreamOutput(
|
||||||
command,
|
command,
|
||||||
workingDirectory: directory,
|
workingDirectory: directory,
|
||||||
mapFunction: filter,
|
mapFunction: filter,
|
||||||
environment: <String, String>{ 'FLUTTER_ROOT': Cache.flutterRoot, _pubEnvironmentKey: _getPubEnvironmentValue() }
|
environment: <String, String>{ 'FLUTTER_ROOT': Cache.flutterRoot, _pubEnvironmentKey: _getPubEnvironmentValue() }
|
||||||
);
|
);
|
||||||
|
if (code != 69) // UNAVAILABLE in https://github.com/dart-lang/pub/blob/master/lib/src/exit_codes.dart
|
||||||
|
break;
|
||||||
|
printStatus('$failureMessage ($code) -- attempting retry $attempts in $duration second${ duration == 1 ? "" : "s"}...');
|
||||||
|
await new Future<Null>.delayed(new Duration(seconds: duration));
|
||||||
|
if (duration < 64)
|
||||||
|
duration *= 2;
|
||||||
|
}
|
||||||
|
assert(code != null);
|
||||||
if (code != 0)
|
if (code != 0)
|
||||||
throwToolExit('$failureMessage ($code)', exitCode: code);
|
throwToolExit('$failureMessage ($code)', exitCode: code);
|
||||||
}
|
}
|
||||||
|
171
packages/flutter_tools/test/dart/pub_get_test.dart
Normal file
171
packages/flutter_tools/test/dart/pub_get_test.dart
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
// 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/base/io.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 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../src/context.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testUsingContext('pub get 69', () async {
|
||||||
|
String error;
|
||||||
|
new FakeAsync().run((FakeAsync time) {
|
||||||
|
pubGet(checkLastModified: false).then((Null value) {
|
||||||
|
error = 'test completed unexpectedly';
|
||||||
|
}, onError: (dynamic error) {
|
||||||
|
error = 'test failed unexpectedly';
|
||||||
|
});
|
||||||
|
expect(testLogger.statusText, '');
|
||||||
|
time.elapse(const Duration(milliseconds: 500));
|
||||||
|
expect(testLogger.statusText,
|
||||||
|
'Running "flutter packages get" in /...\n'
|
||||||
|
'pub get failed (69) -- attempting retry 1 in 1 second...\n'
|
||||||
|
);
|
||||||
|
time.elapse(const Duration(milliseconds: 500));
|
||||||
|
expect(testLogger.statusText,
|
||||||
|
'Running "flutter packages 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 packages 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 packages 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 packages 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: () => new MockProcessManager(69),
|
||||||
|
FileSystem: () => new MockFileSystem(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef void StartCallback(List<dynamic> command);
|
||||||
|
|
||||||
|
class MockProcessManager implements ProcessManager {
|
||||||
|
MockProcessManager(this.fakeExitCode);
|
||||||
|
|
||||||
|
final int fakeExitCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Process> start(
|
||||||
|
List<dynamic> command, {
|
||||||
|
String workingDirectory,
|
||||||
|
Map<String, String> environment,
|
||||||
|
bool includeParentEnvironment: true,
|
||||||
|
bool runInShell: false,
|
||||||
|
ProcessStartMode mode: ProcessStartMode.NORMAL,
|
||||||
|
}) {
|
||||||
|
return new Future<Process>.value(new MockProcess(fakeExitCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic noSuchMethod(Invocation invocation) => null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockProcess implements Process {
|
||||||
|
MockProcess(this.fakeExitCode);
|
||||||
|
|
||||||
|
final int fakeExitCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<int>> get stdout => new MockStream<List<int>>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<int>> get stderr => new MockStream<List<int>>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> get exitCode => new 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) => new MockStream<S>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<T> where(bool test(T event)) => new MockStream<T>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
StreamSubscription<T> listen(void onData(T event), {Function onError, void onDone(), bool cancelOnError}) {
|
||||||
|
return new MockStreamSubscription<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic noSuchMethod(Invocation invocation) => null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockStreamSubscription<T> implements StreamSubscription<T> {
|
||||||
|
@override
|
||||||
|
Future<E> asFuture<E>([E futureValue]) => new Future<E>.value();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Null> cancel() => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic noSuchMethod(Invocation invocation) => null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MockFileSystem extends MemoryFileSystem {
|
||||||
|
@override
|
||||||
|
File file(dynamic path) {
|
||||||
|
return new MockFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockFile implements File {
|
||||||
|
@override
|
||||||
|
Future<RandomAccessFile> open({FileMode mode: FileMode.READ}) async {
|
||||||
|
return new MockRandomAccessFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool existsSync() => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime lastModifiedSync() => new DateTime(0);
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic noSuchMethod(Invocation invocation) => null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockRandomAccessFile extends Mock implements RandomAccessFile {}
|
@ -74,7 +74,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('does not change ASCI casing of path', () {
|
testUsingContext('does not change ASCII casing of path', () {
|
||||||
final String testPath = fs.path.join(dataPath, 'asci_casing');
|
final String testPath = fs.path.join(dataPath, 'asci_casing');
|
||||||
final String mainPath = fs.path.join(testPath, 'main.dart');
|
final String mainPath = fs.path.join(testPath, 'main.dart');
|
||||||
final String packagesPath = fs.path.join(testPath, '.packages');
|
final String packagesPath = fs.path.join(testPath, '.packages');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user