import 'dart:async'; import 'dart:io' show ProcessResult, Process; import 'package:file/file.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/application_package.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/ios/simulators.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; import 'package:process/process.dart'; import 'package:test/test.dart'; import '../src/context.dart'; class MockFile extends Mock implements File {} class MockIMobileDevice extends Mock implements IMobileDevice {} class MockProcess extends Mock implements Process {} class MockProcessManager extends Mock implements ProcessManager {} class MockXcode extends Mock implements Xcode {} void main() { FakePlatform osx; setUp(() { osx = new FakePlatform.fromPlatform(const LocalPlatform()); osx.operatingSystem = 'macos'; }); group('logFilePath', () { testUsingContext('defaults to rooted from HOME', () { osx.environment['HOME'] = '/foo/bar'; expect(new IOSSimulator('123').logFilePath, '/foo/bar/Library/Logs/CoreSimulator/123/system.log'); }, overrides: <Type, Generator>{ Platform: () => osx, }, testOn: 'posix'); testUsingContext('respects IOS_SIMULATOR_LOG_FILE_PATH', () { osx.environment['HOME'] = '/foo/bar'; osx.environment['IOS_SIMULATOR_LOG_FILE_PATH'] = '/baz/qux/%{id}/system.log'; expect(new IOSSimulator('456').logFilePath, '/baz/qux/456/system.log'); }, overrides: <Type, Generator>{ Platform: () => osx, }); }); group('compareIosVersions', () { test('compares correctly', () { // This list must be sorted in ascending preference order final List<String> testList = <String>[ '8', '8.0', '8.1', '8.2', '9', '9.0', '9.1', '9.2', '10', '10.0', '10.1', ]; for (int i = 0; i < testList.length; i++) { expect(compareIosVersions(testList[i], testList[i]), 0); } for (int i = 0; i < testList.length - 1; i++) { for (int j = i + 1; j < testList.length; j++) { expect(compareIosVersions(testList[i], testList[j]), lessThan(0)); expect(compareIosVersions(testList[j], testList[i]), greaterThan(0)); } } }); }); group('compareIphoneVersions', () { test('compares correctly', () { // This list must be sorted in ascending preference order final List<String> testList = <String>[ 'com.apple.CoreSimulator.SimDeviceType.iPhone-4s', 'com.apple.CoreSimulator.SimDeviceType.iPhone-5', 'com.apple.CoreSimulator.SimDeviceType.iPhone-5s', 'com.apple.CoreSimulator.SimDeviceType.iPhone-6strange', 'com.apple.CoreSimulator.SimDeviceType.iPhone-6-Plus', 'com.apple.CoreSimulator.SimDeviceType.iPhone-6', 'com.apple.CoreSimulator.SimDeviceType.iPhone-6s-Plus', 'com.apple.CoreSimulator.SimDeviceType.iPhone-6s', ]; for (int i = 0; i < testList.length; i++) { expect(compareIphoneVersions(testList[i], testList[i]), 0); } for (int i = 0; i < testList.length - 1; i++) { for (int j = i + 1; j < testList.length; j++) { expect(compareIphoneVersions(testList[i], testList[j]), lessThan(0)); expect(compareIphoneVersions(testList[j], testList[i]), greaterThan(0)); } } }); }); group('IOSSimulator.isSupported', () { testUsingContext('Apple TV is unsupported', () { expect(new IOSSimulator('x', name: 'Apple TV').isSupported(), false); }, overrides: <Type, Generator>{ Platform: () => osx, }); testUsingContext('Apple Watch is unsupported', () { expect(new IOSSimulator('x', name: 'Apple Watch').isSupported(), false); }, overrides: <Type, Generator>{ Platform: () => osx, }); testUsingContext('iPad 2 is unsupported', () { expect(new IOSSimulator('x', name: 'iPad 2').isSupported(), false); }, overrides: <Type, Generator>{ Platform: () => osx, }); testUsingContext('iPad Retina is unsupported', () { expect(new IOSSimulator('x', name: 'iPad Retina').isSupported(), false); }, overrides: <Type, Generator>{ Platform: () => osx, }); testUsingContext('iPhone 5 is unsupported', () { expect(new IOSSimulator('x', name: 'iPhone 5').isSupported(), false); }, overrides: <Type, Generator>{ Platform: () => osx, }); testUsingContext('iPhone 5s is supported', () { expect(new IOSSimulator('x', name: 'iPhone 5s').isSupported(), true); }, overrides: <Type, Generator>{ Platform: () => osx, }); testUsingContext('iPhone SE is supported', () { expect(new IOSSimulator('x', name: 'iPhone SE').isSupported(), true); }, overrides: <Type, Generator>{ Platform: () => osx, }); testUsingContext('iPhone 7 Plus is supported', () { expect(new IOSSimulator('x', name: 'iPhone 7 Plus').isSupported(), true); }, overrides: <Type, Generator>{ Platform: () => osx, }); }); group('Simulator screenshot', () { MockXcode mockXcode; MockProcessManager mockProcessManager; IOSSimulator deviceUnderTest; setUp(() { mockXcode = new MockXcode(); mockProcessManager = new MockProcessManager(); // Let everything else return exit code 0 so process.dart doesn't crash. when( mockProcessManager.run(any, environment: null, workingDirectory: null) ).thenAnswer((Invocation invocation) => new Future<ProcessResult>.value(new ProcessResult(2, 0, '', '')) ); // Doesn't matter what the device is. deviceUnderTest = new IOSSimulator('x', name: 'iPhone SE'); }); testUsingContext( 'old Xcode doesn\'t support screenshot', () { when(mockXcode.xcodeMajorVersion).thenReturn(7); when(mockXcode.xcodeMinorVersion).thenReturn(1); expect(deviceUnderTest.supportsScreenshot, false); }, overrides: <Type, Generator>{Xcode: () => mockXcode} ); testUsingContext( 'Xcode 8.2+ supports screenshots', () async { when(mockXcode.xcodeMajorVersion).thenReturn(8); when(mockXcode.xcodeMinorVersion).thenReturn(2); expect(deviceUnderTest.supportsScreenshot, true); final MockFile mockFile = new MockFile(); when(mockFile.path).thenReturn(fs.path.join('some', 'path', 'to', 'screenshot.png')); await deviceUnderTest.takeScreenshot(mockFile); verify(mockProcessManager.run( <String>[ '/usr/bin/xcrun', 'simctl', 'io', 'x', 'screenshot', fs.path.join('some', 'path', 'to', 'screenshot.png'), ], environment: null, workingDirectory: null )); }, overrides: <Type, Generator>{ ProcessManager: () => mockProcessManager, // Test a real one. Screenshot doesn't require instance states. SimControl: () => new SimControl(), Xcode: () => mockXcode, } ); }); group('launchDeviceLogTool', () { MockProcessManager mockProcessManager; setUp(() { mockProcessManager = new MockProcessManager(); when(mockProcessManager.start(any, environment: null, workingDirectory: null)) .thenAnswer((Invocation invocation) => new Future<Process>.value(new MockProcess())); }); testUsingContext('uses tail on iOS versions prior to iOS 11', () async { final IOSSimulator device = new IOSSimulator('x', name: 'iPhone SE', category: 'iOS 9.3'); await launchDeviceLogTool(device); expect( verify(mockProcessManager.start(captureAny, environment: null, workingDirectory: null)).captured.single, contains('tail'), ); }, overrides: <Type, Generator>{ ProcessManager: () => mockProcessManager, }); testUsingContext('uses /usr/bin/log on iOS 11 and above', () async { final IOSSimulator device = new IOSSimulator('x', name: 'iPhone SE', category: 'iOS 11.0'); await launchDeviceLogTool(device); expect( verify(mockProcessManager.start(captureAny, environment: null, workingDirectory: null)).captured.single, contains('/usr/bin/log'), ); }, overrides: <Type, Generator>{ ProcessManager: () => mockProcessManager, }); }); group('launchSystemLogTool', () { MockProcessManager mockProcessManager; setUp(() { mockProcessManager = new MockProcessManager(); when(mockProcessManager.start(any, environment: null, workingDirectory: null)) .thenAnswer((Invocation invocation) => new Future<Process>.value(new MockProcess())); }); testUsingContext('uses tail on iOS versions prior to iOS 11', () async { final IOSSimulator device = new IOSSimulator('x', name: 'iPhone SE', category: 'iOS 9.3'); await launchSystemLogTool(device); expect( verify(mockProcessManager.start(captureAny, environment: null, workingDirectory: null)).captured.single, contains('tail'), ); }, overrides: <Type, Generator>{ ProcessManager: () => mockProcessManager, }); testUsingContext('uses /usr/bin/log on iOS 11 and above', () async { final IOSSimulator device = new IOSSimulator('x', name: 'iPhone SE', category: 'iOS 11.0'); await launchSystemLogTool(device); verifyNever(mockProcessManager.start(any, environment: null, workingDirectory: null)); }, overrides: <Type, Generator>{ ProcessManager: () => mockProcessManager, }); }); group('log reader', () { MockProcessManager mockProcessManager; setUp(() { mockProcessManager = new MockProcessManager(); }); testUsingContext('simulator can output `)`', () async { when(mockProcessManager.start(any, environment: null, workingDirectory: null)) .thenAnswer((Invocation invocation) { final Process mockProcess = new MockProcess(); when(mockProcess.stdout).thenAnswer((Invocation invocation) => new Stream<List<int>>.fromIterable(<List<int>>[''' 2017-09-13 15:26:57.228948-0700 localhost Runner[37195]: (Flutter) Observatory listening on http://127.0.0.1:57701/ 2017-09-13 15:26:57.228948-0700 localhost Runner[37195]: (Flutter) )))))))))) 2017-09-13 15:26:57.228948-0700 localhost Runner[37195]: (Flutter) #0 Object.noSuchMethod (dart:core-patch/dart:core/object_patch.dart:46)''' .codeUnits])); when(mockProcess.stderr) .thenAnswer((Invocation invocation) => const Stream<List<int>>.empty()); // Delay return of exitCode until after stdout stream data, since it terminates the logger. when(mockProcess.exitCode) .thenAnswer((Invocation invocation) => new Future<int>.delayed(Duration.ZERO, () => 0)); return new Future<Process>.value(mockProcess); }) .thenThrow(new TestFailure('Should start one process only')); final IOSSimulator device = new IOSSimulator('123456', category: 'iOS 11.0'); final DeviceLogReader logReader = device.getLogReader( app: new BuildableIOSApp(projectBundleId: 'bundleId'), ); final List<String> lines = await logReader.logLines.toList(); expect(lines, <String>[ 'Observatory listening on http://127.0.0.1:57701/', '))))))))))', '#0 Object.noSuchMethod (dart:core-patch/dart:core/object_patch.dart:46)', ]); }, overrides: <Type, Generator>{ ProcessManager: () => mockProcessManager, }); }); }