mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
980f14e0c5
* implicit-casts:false on flutter_tools * use castStringKeyedMap * address review comments * address review comments * fix issues after rebase
451 lines
17 KiB
Dart
451 lines
17 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 'dart:collection';
|
|
|
|
import 'package:file/file.dart';
|
|
import 'package:file/memory.dart';
|
|
import 'package:flutter_tools/src/base/common.dart';
|
|
import 'package:flutter_tools/src/base/context.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/base/utils.dart';
|
|
import 'package:flutter_tools/src/cache.dart';
|
|
import 'package:flutter_tools/src/dart/pub.dart';
|
|
import 'package:flutter_tools/src/reporting/reporting.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';
|
|
import '../../src/mocks.dart' as mocks;
|
|
import '../../src/testbed.dart';
|
|
|
|
void main() {
|
|
setUpAll(() {
|
|
Cache.flutterRoot = getFlutterRoot();
|
|
});
|
|
|
|
testUsingContext('pub get 69', () async {
|
|
String error;
|
|
|
|
final MockProcessManager processMock = context.get<ProcessManager>() as MockProcessManager;
|
|
|
|
FakeAsync().run((FakeAsync time) {
|
|
expect(processMock.lastPubEnvironment, isNull);
|
|
expect(testLogger.statusText, '');
|
|
pub.get(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 (server unavailable) -- 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 (server unavailable) -- attempting retry 1 in 1 second...\n'
|
|
'pub get failed (server unavailable) -- 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 (server unavailable) -- attempting retry 1 in 1 second...\n'
|
|
'pub get failed (server unavailable) -- 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 (server unavailable) -- attempting retry 1 in 1 second...\n'
|
|
'pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...\n'
|
|
'pub get failed (server unavailable) -- attempting retry 3 in 4 seconds...\n' // at t=1
|
|
'pub get failed (server unavailable) -- attempting retry 4 in 8 seconds...\n' // at t=5
|
|
'pub get failed (server unavailable) -- attempting retry 5 in 16 seconds...\n' // at t=13
|
|
'pub get failed (server unavailable) -- attempting retry 6 in 32 seconds...\n' // at t=29
|
|
'pub get failed (server unavailable) -- 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 (server unavailable) -- attempting retry 1 in 1 second...\n'
|
|
'pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...\n'
|
|
'pub get failed (server unavailable) -- attempting retry 3 in 4 seconds...\n'
|
|
'pub get failed (server unavailable) -- attempting retry 4 in 8 seconds...\n'
|
|
'pub get failed (server unavailable) -- attempting retry 5 in 16 seconds...\n'
|
|
'pub get failed (server unavailable) -- attempting retry 6 in 32 seconds...\n'
|
|
'pub get failed (server unavailable) -- attempting retry 7 in 64 seconds...\n'
|
|
'pub get failed (server unavailable) -- attempting retry 8 in 64 seconds...\n' // at t=39
|
|
'pub get failed (server unavailable) -- attempting retry 9 in 64 seconds...\n' // at t=103
|
|
'pub get failed (server unavailable) -- attempting retry 10 in 64 seconds...\n', // at t=167
|
|
);
|
|
});
|
|
expect(testLogger.errorText, isEmpty);
|
|
expect(error, isNull);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => MockFileSystem(),
|
|
ProcessManager: () => MockProcessManager(69),
|
|
Platform: () => FakePlatform(
|
|
environment: UnmodifiableMapView<String, String>(<String, String>{}),
|
|
),
|
|
Pub: () => const Pub(),
|
|
});
|
|
|
|
testUsingContext('pub get 66 shows message from pub', () async {
|
|
try {
|
|
await pub.get(context: PubContext.flutterTests, checkLastModified: false);
|
|
throw AssertionError('pubGet did not fail');
|
|
} on ToolExit catch (error) {
|
|
expect(error.message, 'pub get failed (66; err3)');
|
|
}
|
|
expect(testLogger.statusText,
|
|
'Running "flutter pub get" in /...\n'
|
|
'out1\n'
|
|
'out2\n'
|
|
'out3\n'
|
|
);
|
|
expect(testLogger.errorText,
|
|
'err1\n'
|
|
'err2\n'
|
|
'err3\n'
|
|
);
|
|
}, overrides: <Type, Generator>{
|
|
ProcessManager: () => MockProcessManager(66, stderr: 'err1\nerr2\nerr3\n', stdout: 'out1\nout2\nout3\n'),
|
|
FileSystem: () => MockFileSystem(),
|
|
Platform: () => FakePlatform(
|
|
environment: UnmodifiableMapView<String, String>(<String, String>{}),
|
|
),
|
|
Pub: () => const Pub(),
|
|
});
|
|
|
|
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);
|
|
pub.get(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>{
|
|
FileSystem: () => MockFileSystem(),
|
|
ProcessManager: () => MockProcessManager(69),
|
|
Platform: () => FakePlatform(
|
|
environment: UnmodifiableMapView<String, String>(<String, String>{}),
|
|
),
|
|
Pub: () => const Pub(),
|
|
});
|
|
|
|
testUsingContext('pub cache in environment is used', () async {
|
|
String error;
|
|
|
|
final MockProcessManager processMock = context.get<ProcessManager>() as MockProcessManager;
|
|
|
|
FakeAsync().run((FakeAsync time) {
|
|
MockDirectory.findCache = true;
|
|
expect(processMock.lastPubEnvironment, isNull);
|
|
expect(processMock.lastPubCache, isNull);
|
|
pub.get(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>{
|
|
FileSystem: () => MockFileSystem(),
|
|
ProcessManager: () => MockProcessManager(69),
|
|
Platform: () => FakePlatform(
|
|
environment: UnmodifiableMapView<String, String>(<String, String>{
|
|
'PUB_CACHE': 'custom/pub-cache/path',
|
|
}),
|
|
),
|
|
Pub: () => const Pub(),
|
|
});
|
|
|
|
testUsingContext('analytics sent on success', () async {
|
|
MockDirectory.findCache = true;
|
|
await pub.get(context: PubContext.flutterTests, checkLastModified: false);
|
|
verify(flutterUsage.sendEvent('pub-result', 'flutter-tests', label: 'success')).called(1);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => MockFileSystem(),
|
|
ProcessManager: () => MockProcessManager(0),
|
|
Platform: () => FakePlatform(
|
|
environment: UnmodifiableMapView<String, String>(<String, String>{
|
|
'PUB_CACHE': 'custom/pub-cache/path',
|
|
}),
|
|
),
|
|
Usage: () => MockUsage(),
|
|
Pub: () => const Pub(),
|
|
});
|
|
|
|
testUsingContext('analytics sent on failure', () async {
|
|
MockDirectory.findCache = true;
|
|
try {
|
|
await pub.get(context: PubContext.flutterTests, checkLastModified: false);
|
|
} on ToolExit {
|
|
// Ignore.
|
|
}
|
|
verify(flutterUsage.sendEvent('pub-result', 'flutter-tests', label: 'failure')).called(1);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => MockFileSystem(),
|
|
ProcessManager: () => MockProcessManager(1),
|
|
Platform: () => FakePlatform(
|
|
environment: UnmodifiableMapView<String, String>(<String, String>{
|
|
'PUB_CACHE': 'custom/pub-cache/path',
|
|
}),
|
|
),
|
|
Usage: () => MockUsage(),
|
|
Pub: () => const Pub(),
|
|
});
|
|
|
|
testUsingContext('analytics sent on failed version solve', () async {
|
|
MockDirectory.findCache = true;
|
|
try {
|
|
await pub.get(context: PubContext.flutterTests, checkLastModified: false);
|
|
} on ToolExit {
|
|
// Ignore.
|
|
}
|
|
verify(flutterUsage.sendEvent('pub-result', 'flutter-tests', label: 'version-solving-failed')).called(1);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => MockFileSystem(),
|
|
ProcessManager: () => MockProcessManager(
|
|
1,
|
|
stderr: 'version solving failed',
|
|
),
|
|
Platform: () => FakePlatform(
|
|
environment: UnmodifiableMapView<String, String>(<String, String>{
|
|
'PUB_CACHE': 'custom/pub-cache/path',
|
|
}),
|
|
),
|
|
Usage: () => MockUsage(),
|
|
Pub: () => const Pub(),
|
|
});
|
|
|
|
test('Pub error handling', () async {
|
|
final MemoryFileSystem fileSystem = MemoryFileSystem();
|
|
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'/bin/cache/dart-sdk/bin/pub',
|
|
'--verbosity=warning',
|
|
'get',
|
|
'--no-precompile',
|
|
],
|
|
onRun: () {
|
|
fs.file('.packages')
|
|
..setLastModifiedSync(DateTime(2002));
|
|
}
|
|
),
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'/bin/cache/dart-sdk/bin/pub',
|
|
'--verbosity=warning',
|
|
'get',
|
|
'--no-precompile',
|
|
],
|
|
),
|
|
FakeCommand(
|
|
command: const <String>[
|
|
'/bin/cache/dart-sdk/bin/pub',
|
|
'--verbosity=warning',
|
|
'get',
|
|
'--no-precompile',
|
|
],
|
|
onRun: () {
|
|
fs.file('pubspec.yaml')
|
|
..setLastModifiedSync(DateTime(2002));
|
|
}
|
|
),
|
|
const FakeCommand(
|
|
command: <String>[
|
|
'/bin/cache/dart-sdk/bin/pub',
|
|
'--verbosity=warning',
|
|
'get',
|
|
'--no-precompile',
|
|
],
|
|
),
|
|
]);
|
|
await Testbed().run(() async {
|
|
// the good scenario: .packages is old, pub updates the file.
|
|
fs.file('.packages')
|
|
..createSync()
|
|
..setLastModifiedSync(DateTime(2000));
|
|
fs.file('pubspec.yaml')
|
|
..createSync()
|
|
..setLastModifiedSync(DateTime(2001));
|
|
await pub.get(context: PubContext.flutterTests, checkLastModified: true); // pub sets date of .packages to 2002
|
|
expect(testLogger.statusText, 'Running "flutter pub get" in /...\n');
|
|
expect(testLogger.errorText, isEmpty);
|
|
expect(fs.file('pubspec.yaml').lastModifiedSync(), DateTime(2001)); // because nothing should touch it
|
|
expect(fs.file('.packages').lastModifiedSync(), isNot(DateTime(2000))); // because pub changes it to 2002
|
|
expect(fs.file('.packages').lastModifiedSync(), isNot(DateTime(2002))); // because we set the timestamp again after pub
|
|
testLogger.clear();
|
|
// bad scenario 1: pub doesn't update file; doesn't matter, because we do instead
|
|
fs.file('.packages')
|
|
..setLastModifiedSync(DateTime(2000));
|
|
fs.file('pubspec.yaml')
|
|
..setLastModifiedSync(DateTime(2001));
|
|
await pub.get(context: PubContext.flutterTests, checkLastModified: true); // pub does nothing
|
|
expect(testLogger.statusText, 'Running "flutter pub get" in /...\n');
|
|
expect(testLogger.errorText, isEmpty);
|
|
expect(fs.file('pubspec.yaml').lastModifiedSync(), DateTime(2001)); // because nothing should touch it
|
|
expect(fs.file('.packages').lastModifiedSync(), isNot(DateTime(2000))); // because we set the timestamp
|
|
expect(fs.file('.packages').lastModifiedSync(), isNot(DateTime(2002))); // just in case FakeProcessManager is buggy
|
|
testLogger.clear();
|
|
// bad scenario 2: pub changes pubspec.yaml instead
|
|
fs.file('.packages')
|
|
..setLastModifiedSync(DateTime(2000));
|
|
fs.file('pubspec.yaml')
|
|
..setLastModifiedSync(DateTime(2001));
|
|
try {
|
|
await pub.get(context: PubContext.flutterTests, checkLastModified: true);
|
|
expect(true, isFalse, reason: 'pub.get did not throw');
|
|
} catch (error) {
|
|
expect(error, isInstanceOf<Exception>());
|
|
expect(error.message, '/: unexpected concurrent modification of pubspec.yaml while running pub.');
|
|
}
|
|
expect(testLogger.statusText, 'Running "flutter pub get" in /...\n');
|
|
expect(testLogger.errorText, isEmpty);
|
|
expect(fs.file('pubspec.yaml').lastModifiedSync(), DateTime(2002)); // because fake pub above touched it
|
|
expect(fs.file('.packages').lastModifiedSync(), DateTime(2000)); // because nothing touched it
|
|
// bad scenario 3: pubspec.yaml was created in the future
|
|
fs.file('.packages')
|
|
..setLastModifiedSync(DateTime(2000));
|
|
fs.file('pubspec.yaml')
|
|
..setLastModifiedSync(DateTime(9999));
|
|
assert(DateTime(9999).isAfter(DateTime.now()));
|
|
await pub.get(context: PubContext.flutterTests, checkLastModified: true); // pub does nothing
|
|
expect(testLogger.statusText, contains('Running "flutter pub get" in /...\n'));
|
|
expect(testLogger.errorText, startsWith(
|
|
'Warning: File "/pubspec.yaml" was created in the future. Optimizations that rely on '
|
|
'comparing time stamps will be unreliable. Check your system clock for accuracy.\n'
|
|
'The timestamp was:'
|
|
));
|
|
testLogger.clear();
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: () => const Pub(),
|
|
Platform: () => FakePlatform(
|
|
operatingSystem: 'linux', // so that the command executed is consistent
|
|
environment: <String, String>{},
|
|
),
|
|
BotDetector: () => const BotDetectorAlwaysNo(), // so that the test never adds --trace to the pub command
|
|
});
|
|
});
|
|
}
|
|
|
|
class BotDetectorAlwaysNo implements BotDetector {
|
|
const BotDetectorAlwaysNo();
|
|
@override
|
|
bool get isRunningOnBot => false;
|
|
}
|
|
|
|
typedef StartCallback = void Function(List<dynamic> command);
|
|
|
|
class MockProcessManager implements ProcessManager {
|
|
MockProcessManager(this.fakeExitCode, {
|
|
this.stdout = '',
|
|
this.stderr = '',
|
|
});
|
|
|
|
final int fakeExitCode;
|
|
final String stdout;
|
|
final String stderr;
|
|
|
|
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(mocks.createMockProcess(
|
|
exitCode: fakeExitCode,
|
|
stdout: stdout,
|
|
stderr: stderr,
|
|
));
|
|
}
|
|
|
|
@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 as String);
|
|
}
|
|
}
|
|
|
|
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 {}
|
|
|
|
class MockUsage extends Mock implements Usage {}
|