forked from firka/flutter
We used to use zip to verify the integrity of downloaded zip archives, but we now use unzip. This removes the .zip method from OperatingSystemUtils.
467 lines
14 KiB
Dart
467 lines
14 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:flutter_tools/src/android/android_workflow.dart';
|
|
import 'package:flutter_tools/src/base/bot_detector.dart';
|
|
import 'package:flutter_tools/src/base/config.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/logger.dart';
|
|
import 'package:flutter_tools/src/base/os.dart';
|
|
import 'package:flutter_tools/src/base/process.dart';
|
|
import 'package:flutter_tools/src/base/signals.dart';
|
|
import 'package:flutter_tools/src/base/template.dart';
|
|
import 'package:flutter_tools/src/base/terminal.dart';
|
|
import 'package:flutter_tools/src/base/time.dart';
|
|
import 'package:flutter_tools/src/isolated/mustache_template.dart';
|
|
import 'package:flutter_tools/src/cache.dart';
|
|
import 'package:flutter_tools/src/context_runner.dart';
|
|
import 'package:flutter_tools/src/dart/pub.dart';
|
|
import 'package:flutter_tools/src/device.dart';
|
|
import 'package:flutter_tools/src/doctor.dart';
|
|
import 'package:flutter_tools/src/ios/plist_parser.dart';
|
|
import 'package:flutter_tools/src/ios/simulators.dart';
|
|
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
|
import 'package:flutter_tools/src/persistent_tool_state.dart';
|
|
import 'package:flutter_tools/src/project.dart';
|
|
import 'package:flutter_tools/src/reporting/reporting.dart';
|
|
import 'package:flutter_tools/src/version.dart';
|
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
|
import 'package:meta/meta.dart';
|
|
import 'package:mockito/mockito.dart';
|
|
|
|
import 'common.dart';
|
|
import 'fake_process_manager.dart';
|
|
import 'mocks.dart';
|
|
import 'throwing_pub.dart';
|
|
|
|
export 'package:flutter_tools/src/base/context.dart' show Generator;
|
|
export 'fake_process_manager.dart' show ProcessManager, FakeProcessManager, FakeCommand;
|
|
|
|
/// Return the test logger. This assumes that the current Logger is a BufferLogger.
|
|
BufferLogger get testLogger => context.get<Logger>() as BufferLogger;
|
|
|
|
FakeDeviceManager get testDeviceManager => context.get<DeviceManager>() as FakeDeviceManager;
|
|
FakeDoctor get testDoctor => context.get<Doctor>() as FakeDoctor;
|
|
|
|
typedef ContextInitializer = void Function(AppContext testContext);
|
|
|
|
@isTest
|
|
void testUsingContext(
|
|
String description,
|
|
dynamic testMethod(), {
|
|
Map<Type, Generator> overrides = const <Type, Generator>{},
|
|
bool initializeFlutterRoot = true,
|
|
String testOn,
|
|
bool skip, // should default to `false`, but https://github.com/dart-lang/test/issues/545 doesn't allow this
|
|
}) {
|
|
if (overrides[FileSystem] != null && overrides[ProcessManager] == null) {
|
|
throw StateError(
|
|
'If you override the FileSystem context you must also provide a ProcessManager, '
|
|
'otherwise the processes you launch will not be dealing with the same file system '
|
|
'that you are dealing with in your test.'
|
|
);
|
|
}
|
|
if (overrides.containsKey(ProcessUtils)) {
|
|
throw StateError('Do not inject ProcessUtils for testing, use ProcessManager instead.');
|
|
}
|
|
|
|
// Ensure we don't rely on the default [Config] constructor which will
|
|
// leak a sticky $HOME/.flutter_settings behind!
|
|
Directory configDir;
|
|
tearDown(() {
|
|
if (configDir != null) {
|
|
tryToDelete(configDir);
|
|
configDir = null;
|
|
}
|
|
});
|
|
Config buildConfig(FileSystem fs) {
|
|
configDir ??= globals.fs.systemTempDirectory.createTempSync(
|
|
'flutter_config_dir_test.',
|
|
);
|
|
return Config.test(
|
|
Config.kFlutterSettings,
|
|
directory: configDir,
|
|
logger: globals.logger,
|
|
);
|
|
}
|
|
PersistentToolState buildPersistentToolState(FileSystem fs) {
|
|
configDir ??= globals.fs.systemTempDirectory.createTempSync(
|
|
'flutter_config_dir_test.',
|
|
);
|
|
return PersistentToolState.test(
|
|
directory: configDir,
|
|
logger: globals.logger,
|
|
);
|
|
}
|
|
|
|
test(description, () async {
|
|
await runInContext<dynamic>(() {
|
|
return context.run<dynamic>(
|
|
name: 'mocks',
|
|
overrides: <Type, Generator>{
|
|
AnsiTerminal: () => AnsiTerminal(platform: globals.platform, stdio: globals.stdio),
|
|
Config: () => buildConfig(globals.fs),
|
|
DeviceManager: () => FakeDeviceManager(),
|
|
Doctor: () => FakeDoctor(globals.logger),
|
|
FlutterVersion: () => MockFlutterVersion(),
|
|
HttpClient: () => MockHttpClient(),
|
|
IOSSimulatorUtils: () {
|
|
final MockIOSSimulatorUtils mock = MockIOSSimulatorUtils();
|
|
when(mock.getAttachedDevices()).thenAnswer((Invocation _) async => <IOSSimulator>[]);
|
|
return mock;
|
|
},
|
|
OutputPreferences: () => OutputPreferences.test(),
|
|
Logger: () => BufferLogger(
|
|
terminal: globals.terminal,
|
|
outputPreferences: globals.outputPreferences,
|
|
),
|
|
OperatingSystemUtils: () => FakeOperatingSystemUtils(),
|
|
PersistentToolState: () => buildPersistentToolState(globals.fs),
|
|
SimControl: () => MockSimControl(),
|
|
Usage: () => FakeUsage(),
|
|
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(),
|
|
FileSystem: () => LocalFileSystemBlockingSetCurrentDirectory(),
|
|
TimeoutConfiguration: () => const TimeoutConfiguration(),
|
|
PlistParser: () => FakePlistParser(),
|
|
Signals: () => FakeSignals(),
|
|
Pub: () => ThrowingPub(), // prevent accidentally using pub.
|
|
CrashReporter: () => MockCrashReporter(),
|
|
TemplateRenderer: () => const MustacheTemplateRenderer(),
|
|
},
|
|
body: () {
|
|
final String flutterRoot = getFlutterRoot();
|
|
return runZoned<Future<dynamic>>(() {
|
|
try {
|
|
return context.run<dynamic>(
|
|
// Apply the overrides to the test context in the zone since their
|
|
// instantiation may reference items already stored on the context.
|
|
overrides: overrides,
|
|
name: 'test-specific overrides',
|
|
body: () async {
|
|
if (initializeFlutterRoot) {
|
|
// Provide a sane default for the flutterRoot directory. Individual
|
|
// tests can override this either in the test or during setup.
|
|
Cache.flutterRoot ??= flutterRoot;
|
|
}
|
|
return await testMethod();
|
|
},
|
|
);
|
|
// This catch rethrows, so doesn't need to catch only Exception.
|
|
} catch (error) { // ignore: avoid_catches_without_on_clauses
|
|
_printBufferedErrors(context);
|
|
rethrow;
|
|
}
|
|
}, onError: (Object error, StackTrace stackTrace) { // ignore: deprecated_member_use
|
|
print(error);
|
|
print(stackTrace);
|
|
_printBufferedErrors(context);
|
|
throw error;
|
|
});
|
|
},
|
|
);
|
|
}, overrides: <Type, Generator>{
|
|
// This has to go here so that runInContext will pick it up when it tries
|
|
// to do bot detection before running the closure. This is important
|
|
// because the test may be giving us a fake HttpClientFactory, which may
|
|
// throw in unexpected/abnormal ways.
|
|
// If a test needs a BotDetector that does not always return true, it
|
|
// can provide the AlwaysFalseBotDetector in the overrides, or its own
|
|
// BotDetector implementation in the overrides.
|
|
BotDetector: overrides[BotDetector] ?? () => const AlwaysTrueBotDetector(),
|
|
});
|
|
}, testOn: testOn, skip: skip);
|
|
}
|
|
|
|
void _printBufferedErrors(AppContext testContext) {
|
|
if (testContext.get<Logger>() is BufferLogger) {
|
|
final BufferLogger bufferLogger = testContext.get<Logger>() as BufferLogger;
|
|
if (bufferLogger.errorText.isNotEmpty) {
|
|
print(bufferLogger.errorText);
|
|
}
|
|
bufferLogger.clear();
|
|
}
|
|
}
|
|
|
|
class FakeDeviceManager implements DeviceManager {
|
|
List<Device> devices = <Device>[];
|
|
|
|
String _specifiedDeviceId;
|
|
|
|
@override
|
|
String get specifiedDeviceId {
|
|
if (_specifiedDeviceId == null || _specifiedDeviceId == 'all') {
|
|
return null;
|
|
}
|
|
return _specifiedDeviceId;
|
|
}
|
|
|
|
@override
|
|
set specifiedDeviceId(String id) {
|
|
_specifiedDeviceId = id;
|
|
}
|
|
|
|
@override
|
|
bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
|
|
|
|
@override
|
|
bool get hasSpecifiedAllDevices {
|
|
return _specifiedDeviceId != null && _specifiedDeviceId == 'all';
|
|
}
|
|
|
|
@override
|
|
Future<List<Device>> getAllConnectedDevices() async => devices;
|
|
|
|
@override
|
|
Future<List<Device>> refreshAllConnectedDevices({ Duration timeout }) async => devices;
|
|
|
|
@override
|
|
Future<List<Device>> getDevicesById(String deviceId) async {
|
|
return devices.where((Device device) => device.id == deviceId).toList();
|
|
}
|
|
|
|
@override
|
|
Future<List<Device>> getDevices() {
|
|
return hasSpecifiedDeviceId
|
|
? getDevicesById(specifiedDeviceId)
|
|
: getAllConnectedDevices();
|
|
}
|
|
|
|
void addDevice(Device device) => devices.add(device);
|
|
|
|
@override
|
|
bool get canListAnything => true;
|
|
|
|
@override
|
|
Future<List<String>> getDeviceDiagnostics() async => <String>[];
|
|
|
|
@override
|
|
List<DeviceDiscovery> get deviceDiscoverers => <DeviceDiscovery>[];
|
|
|
|
@override
|
|
bool isDeviceSupportedForProject(Device device, FlutterProject flutterProject) {
|
|
return device.isSupportedForProject(flutterProject);
|
|
}
|
|
|
|
@override
|
|
Future<List<Device>> findTargetDevices(FlutterProject flutterProject, { Duration timeout }) async {
|
|
return devices;
|
|
}
|
|
}
|
|
|
|
class FakeAndroidLicenseValidator extends AndroidLicenseValidator {
|
|
@override
|
|
Future<LicensesAccepted> get licensesAccepted async => LicensesAccepted.all;
|
|
}
|
|
|
|
class FakeDoctor extends Doctor {
|
|
FakeDoctor(Logger logger) : super(logger: logger);
|
|
|
|
// True for testing.
|
|
@override
|
|
bool get canListAnything => true;
|
|
|
|
// True for testing.
|
|
@override
|
|
bool get canLaunchAnything => true;
|
|
|
|
@override
|
|
/// Replaces the android workflow with a version that overrides licensesAccepted,
|
|
/// to prevent individual tests from having to mock out the process for
|
|
/// the Doctor.
|
|
List<DoctorValidator> get validators {
|
|
final List<DoctorValidator> superValidators = super.validators;
|
|
return superValidators.map<DoctorValidator>((DoctorValidator v) {
|
|
if (v is AndroidLicenseValidator) {
|
|
return FakeAndroidLicenseValidator();
|
|
}
|
|
return v;
|
|
}).toList();
|
|
}
|
|
}
|
|
|
|
class MockSimControl extends Mock implements SimControl {
|
|
MockSimControl() {
|
|
when(getConnectedDevices()).thenAnswer((Invocation _) async => <SimDevice>[]);
|
|
}
|
|
}
|
|
|
|
class FakeOperatingSystemUtils implements OperatingSystemUtils {
|
|
@override
|
|
ProcessResult makeExecutable(File file) => null;
|
|
|
|
@override
|
|
void chmod(FileSystemEntity entity, String mode) { }
|
|
|
|
@override
|
|
File which(String execName) => null;
|
|
|
|
@override
|
|
List<File> whichAll(String execName) => <File>[];
|
|
|
|
@override
|
|
File makePipe(String path) => null;
|
|
|
|
@override
|
|
void unzip(File file, Directory targetDirectory) { }
|
|
|
|
@override
|
|
void unpack(File gzippedTarFile, Directory targetDirectory) { }
|
|
|
|
@override
|
|
Stream<List<int>> gzipLevel1Stream(Stream<List<int>> stream) => stream;
|
|
|
|
@override
|
|
String get name => 'fake OS name and version';
|
|
|
|
@override
|
|
String get pathVarSeparator => ';';
|
|
|
|
@override
|
|
Future<int> findFreePort({bool ipv6 = false}) async => 12345;
|
|
}
|
|
|
|
class MockIOSSimulatorUtils extends Mock implements IOSSimulatorUtils {}
|
|
|
|
class FakeUsage implements Usage {
|
|
@override
|
|
bool get isFirstRun => false;
|
|
|
|
@override
|
|
bool get suppressAnalytics => false;
|
|
|
|
@override
|
|
set suppressAnalytics(bool value) { }
|
|
|
|
@override
|
|
bool get enabled => true;
|
|
|
|
@override
|
|
set enabled(bool value) { }
|
|
|
|
@override
|
|
String get clientId => '00000000-0000-4000-0000-000000000000';
|
|
|
|
@override
|
|
void sendCommand(String command, { Map<String, String> parameters }) { }
|
|
|
|
@override
|
|
void sendEvent(String category, String parameter, {
|
|
String label,
|
|
int value,
|
|
Map<String, String> parameters,
|
|
}) { }
|
|
|
|
@override
|
|
void sendTiming(String category, String variableName, Duration duration, { String label }) { }
|
|
|
|
@override
|
|
void sendException(dynamic exception) { }
|
|
|
|
@override
|
|
Stream<Map<String, dynamic>> get onSend => null;
|
|
|
|
@override
|
|
Future<void> ensureAnalyticsSent() => Future<void>.value();
|
|
|
|
@override
|
|
void printWelcome() { }
|
|
}
|
|
|
|
class FakeXcodeProjectInterpreter implements XcodeProjectInterpreter {
|
|
@override
|
|
bool get isInstalled => true;
|
|
|
|
@override
|
|
String get versionText => 'Xcode 11.0';
|
|
|
|
@override
|
|
int get majorVersion => 11;
|
|
|
|
@override
|
|
int get minorVersion => 0;
|
|
|
|
@override
|
|
int get patchVersion => 0;
|
|
|
|
@override
|
|
Future<Map<String, String>> getBuildSettings(
|
|
String projectPath, {
|
|
String scheme,
|
|
Duration timeout = const Duration(minutes: 1),
|
|
}) async {
|
|
return <String, String>{};
|
|
}
|
|
|
|
@override
|
|
Future<void> cleanWorkspace(String workspacePath, String scheme, { bool verbose = false }) {
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
Future<XcodeProjectInfo> getInfo(String projectPath, {String projectFilename}) async {
|
|
return XcodeProjectInfo(
|
|
<String>['Runner'],
|
|
<String>['Debug', 'Release'],
|
|
<String>['Runner'],
|
|
BufferLogger.test(),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MockFlutterVersion extends Mock implements FlutterVersion {
|
|
MockFlutterVersion({bool isStable = false}) : _isStable = isStable;
|
|
|
|
final bool _isStable;
|
|
|
|
@override
|
|
bool get isMaster => !_isStable;
|
|
}
|
|
|
|
class MockClock extends Mock implements SystemClock {}
|
|
|
|
class MockHttpClient extends Mock implements HttpClient {}
|
|
|
|
class MockCrashReporter extends Mock implements CrashReporter {}
|
|
|
|
class FakePlistParser implements PlistParser {
|
|
@override
|
|
Map<String, dynamic> parseFile(String plistFilePath) => const <String, dynamic>{};
|
|
|
|
@override
|
|
String getValueFromFile(String plistFilePath, String key) => null;
|
|
}
|
|
|
|
class LocalFileSystemBlockingSetCurrentDirectory extends LocalFileSystem {
|
|
LocalFileSystemBlockingSetCurrentDirectory() : super.test(
|
|
signals: LocalSignals.instance,
|
|
);
|
|
|
|
@override
|
|
set currentDirectory(dynamic value) {
|
|
throw 'globals.fs.currentDirectory should not be set on the local file system during '
|
|
'tests as this can cause race conditions with concurrent tests. '
|
|
'Consider using a MemoryFileSystem for testing if possible or refactor '
|
|
'code to not require setting globals.fs.currentDirectory.';
|
|
}
|
|
}
|
|
|
|
class FakeSignals implements Signals {
|
|
@override
|
|
Object addHandler(ProcessSignal signal, SignalHandler handler) {
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
Future<bool> removeHandler(ProcessSignal signal, Object token) async {
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Stream<Object> get errors => const Stream<Object>.empty();
|
|
}
|