blob: e1ddfb27ba1a3378a2b00c49034d12dacb77eb86 [file] [log] [blame]
// Copyright 2020 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:args/command_runner.dart';
import 'package:device_doctor/src/ios_debug_symbol_doctor.dart';
import 'package:fake_async/fake_async.dart';
import 'package:file/memory.dart';
import 'package:logging/logging.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'utils.dart';
Future<void> main() async {
test('XCDevice surfaces "Fetching debug symbols" error messages', () {
final Iterable<XCDevice> devices = XCDevice.parseJson(_jsonWithErrors);
final Iterable<XCDevice> erroredDevices = devices.where((XCDevice device) {
return device.hasError;
});
expect(erroredDevices, hasLength(1));
final XCDevice erroredDevice = erroredDevices.single;
expect(erroredDevice.error!['code'], -10);
expect(erroredDevice.error!['failureReason'], isEmpty);
expect(erroredDevice.error!['description'], 'iPhone is busy: Fetching debug symbols for iPhone');
expect(erroredDevice.error!['recoverySuggestion'], 'Xcode will continue when iPhone is finished.');
expect(erroredDevice.error!['domain'], 'com.apple.platform.iphoneos');
});
test('XCDevice ignores "phone is locked" errors', () {
final Iterable<XCDevice> devices = XCDevice.parseJson(_jsonWithNonFatalErrors);
final Iterable<XCDevice> erroredDevices = devices.where((XCDevice device) {
return device.hasError;
});
expect(erroredDevices, isEmpty);
});
CommandRunner<bool> _createTestRunner() {
return CommandRunner(
'ios-debug-symbol-doctor',
'for testing',
);
}
group('commands', () {
late MockProcessManager processManager;
late TestLogger logger;
const String cocoonPath = '/path/to/cocoon';
const String xcworkspacePath = '$cocoonPath/dashboard/ios/Runner.xcodeproj/project.xcworkspace';
late MemoryFileSystem fs;
setUp(() {
processManager = MockProcessManager();
logger = TestLogger();
fs = MemoryFileSystem();
fs.directory(xcworkspacePath).createSync(recursive: true);
});
test('diagnose logs output of xcdevice list', () async {
when(
processManager.run(<String>['xcrun', 'xcdevice', 'list']),
).thenAnswer((_) async {
return ProcessResult(0, 0, _jsonWithNonFatalErrors, '');
});
final CommandRunner<bool> runner = _createTestRunner();
final command = DiagnoseCommand(
processManager: processManager,
loggerOverride: logger,
);
runner.addCommand(command);
await runner.run(<String>['diagnose']);
expect(logger.logs[Level.INFO], contains(_jsonWithNonFatalErrors));
});
test('recover opens Xcode, waits, then kills it', () async {
when(
processManager.run(<String>['open', '-n', '-F', '-W', xcworkspacePath]),
).thenAnswer((_) async {
return ProcessResult(1, 0, '', '');
});
bool killedXcode = false;
when(
processManager.run(<String>['killall', '-9', 'Xcode']),
).thenAnswer((_) async {
killedXcode = true;
return ProcessResult(2, 0, '', '');
});
final bool result = fakeAsync<bool>((FakeAsync time) {
final CommandRunner<bool> runner = _createTestRunner();
final command = RecoverCommand(
processManager: processManager,
loggerOverride: logger,
fs: fs,
);
runner.addCommand(command);
bool? result;
runner.run(<String>['recover', '--cocoon-root=$cocoonPath']).then((bool? value) => result = value);
time.elapse(const Duration(seconds: 299));
// We have not yet reached the timeout, so Xcode should still be open
expect(result, isNull);
expect(killedXcode, isFalse);
time.elapse(const Duration(seconds: 2));
expect(result, isNotNull);
expect(killedXcode, isTrue);
return result!;
});
expect(result, isTrue);
expect(
logger.logs[Level.INFO],
containsAllInOrder(<String>[
'Launching Xcode...',
'Waiting for 300 seconds',
'Waited for 300 seconds, now killing Xcode',
]));
});
});
}
const String _jsonWithNonFatalErrors = '''
[
{
"modelCode" : "iPhone11,8",
"simulator" : false,
"modelName" : "iPhone XR",
"error" : {
"code" : -13,
"failureReason" : "",
"underlyingErrors" : [
{
"code" : 4,
"failureReason" : "",
"description" : "Flutter’s iPhone is locked.",
"recoverySuggestion" : "To use Flutter’s iPhone with Xcode, unlock it.",
"domain" : "DVTDeviceIneligibilityErrorDomain"
}
],
"description" : "Flutter’s iPhone is not connected",
"recoverySuggestion" : "Xcode will continue when Flutter’s iPhone is connected.",
"domain" : "com.apple.platform.iphoneos"
},
"operatingSystemVersion" : "15.4.1 (19E258)",
"identifier" : "00008120-00017DA80CC1002E",
"platform" : "com.apple.platform.iphoneos",
"architecture" : "arm64e",
"interface" : "usb",
"available" : false,
"name" : "Flutter’s iPhone",
"modelUTI" : "com.apple.iphone-xr-9"
}
]''';
const String _jsonWithErrors = '''
[
{
"modelCode" : "iPhone8,1",
"simulator" : false,
"modelName" : "iPhone 6s",
"error" : {
"code" : -10,
"failureReason" : "",
"description" : "iPhone is busy: Fetching debug symbols for iPhone",
"recoverySuggestion" : "Xcode will continue when iPhone is finished.",
"domain" : "com.apple.platform.iphoneos"
},
"operatingSystemVersion" : "15.1 (19B74)",
"identifier" : "e3f3a0cf8005b8b34f14d16fa224b19017648353",
"platform" : "com.apple.platform.iphoneos",
"architecture" : "arm64",
"interface" : "usb",
"available" : false,
"name" : "iPhone",
"modelUTI" : "com.apple.iphone-6s-e1ccb7"
}
]
''';