// Copyright 2017 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/ios/cocoapods.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:mockito/mockito.dart'; import 'package:process/process.dart'; import 'package:test/test.dart'; import '../src/context.dart'; void main() { FileSystem fs; ProcessManager mockProcessManager; Directory projectUnderTest; CocoaPods cocoaPodsUnderTest; setUp(() { Cache.flutterRoot = 'flutter'; fs = new MemoryFileSystem(); mockProcessManager = new MockProcessManager(); projectUnderTest = fs.directory(fs.path.join('project', 'ios'))..createSync(recursive: true); fs.file(fs.path.join( Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'cocoapods', 'Podfile-objc' )) ..createSync(recursive: true) ..writeAsStringSync('Objective-C podfile template'); fs.file(fs.path.join( Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'cocoapods', 'Podfile-swift' )) ..createSync(recursive: true) ..writeAsStringSync('Swift podfile template'); cocoaPodsUnderTest = const TestCocoaPods(); when(mockProcessManager.run( ['pod', 'install', '--verbose'], workingDirectory: 'project/ios', environment: {'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'}, )).thenReturn(exitsHappy); }); testUsingContext( 'create objective-c Podfile when not present', () async { await cocoaPodsUnderTest.processPods( appIosDir: projectUnderTest, iosEngineDir: 'engine/path', ); expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Objective-C podfile template'); verify(mockProcessManager.run( ['pod', 'install', '--verbose'], workingDirectory: 'project/ios', environment: {'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'}, )); }, overrides: { FileSystem: () => fs, ProcessManager: () => mockProcessManager, }, ); testUsingContext( 'create swift Podfile if swift', () async { await cocoaPodsUnderTest.processPods( appIosDir: projectUnderTest, iosEngineDir: 'engine/path', isSwift: true, ); expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Swift podfile template'); verify(mockProcessManager.run( ['pod', 'install', '--verbose'], workingDirectory: 'project/ios', environment: {'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'}, )); }, overrides: { FileSystem: () => fs, ProcessManager: () => mockProcessManager, }, ); testUsingContext( 'do not recreate Podfile when present', () async { fs.file(fs.path.join('project', 'ios', 'Podfile')) ..createSync() ..writeAsString('Existing Podfile'); await cocoaPodsUnderTest.processPods( appIosDir: projectUnderTest, iosEngineDir: 'engine/path', ); expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Existing Podfile'); verify(mockProcessManager.run( ['pod', 'install', '--verbose'], workingDirectory: 'project/ios', environment: {'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'}, )); }, overrides: { FileSystem: () => fs, ProcessManager: () => mockProcessManager, }, ); testUsingContext( 'missing CocoaPods throws', () async { cocoaPodsUnderTest = const TestCocoaPods(false); try { await cocoaPodsUnderTest.processPods( appIosDir: projectUnderTest, iosEngineDir: 'engine/path', ); fail('Expected tool error'); } catch (ToolExit) { verifyNever(mockProcessManager.run( ['pod', 'install', '--verbose'], workingDirectory: 'project/ios', environment: {'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'}, )); } }, overrides: { FileSystem: () => fs, ProcessManager: () => mockProcessManager, }, ); testUsingContext( 'outdated specs repo should print error', () async { fs.file(fs.path.join('project', 'ios', 'Podfile')) ..createSync() ..writeAsString('Existing Podfile'); when(mockProcessManager.run( ['pod', 'install', '--verbose'], workingDirectory: 'project/ios', environment: {'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'}, )).thenReturn(new ProcessResult( 1, 1, ''' [!] Unable to satisfy the following requirements: - `Firebase/Auth` required by `Podfile` - `Firebase/Auth (= 4.0.0)` required by `Podfile.lock` None of your spec sources contain a spec satisfying the dependencies: `Firebase/Auth, Firebase/Auth (= 4.0.0)`. You have either: * out-of-date source repos which you can update with `pod repo update` or with `pod install --repo-update`. * mistyped the name or version. * not added the source repo that hosts the Podspec to your Podfile. Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by default.''', '', )); try { await cocoaPodsUnderTest.processPods( appIosDir: projectUnderTest, iosEngineDir: 'engine/path', ); expect(fs.file(fs.path.join('project', 'ios', 'Podfile')).readAsStringSync() , 'Existing Podfile'); fail('Exception expected'); } catch (ToolExit) { expect(testLogger.errorText, contains("CocoaPods's specs repository is too out-of-date to satisfy dependencies")); } }, overrides: { FileSystem: () => fs, ProcessManager: () => mockProcessManager, }, ); testUsingContext( 'Run pod install if plugins or flutter framework have changes.', () async { fs.file(fs.path.join('project', 'ios', 'Podfile')) ..createSync() ..writeAsString('Existing Podfile'); fs.file(fs.path.join('project', 'ios', 'Podfile.lock')) ..createSync() ..writeAsString('Existing lock files.'); fs.file(fs.path.join('project', 'ios', 'Pods','Manifest.lock')) ..createSync(recursive: true) ..writeAsString('Existing lock files.'); await cocoaPodsUnderTest.processPods( appIosDir: projectUnderTest, iosEngineDir: 'engine/path', pluginOrFlutterPodChanged: true ); verify(mockProcessManager.run( ['pod', 'install', '--verbose'], workingDirectory: 'project/ios', environment: {'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'}, )); }, overrides: { FileSystem: () => fs, ProcessManager: () => mockProcessManager, }, ); testUsingContext( 'Skip pod install if plugins and flutter framework remain unchanged.', () async { fs.file(fs.path.join('project', 'ios', 'Podfile')) ..createSync() ..writeAsString('Existing Podfile'); fs.file(fs.path.join('project', 'ios', 'Podfile.lock')) ..createSync() ..writeAsString('Existing lock files.'); fs.file(fs.path.join('project', 'ios', 'Pods','Manifest.lock')) ..createSync(recursive: true) ..writeAsString('Existing lock files.'); await cocoaPodsUnderTest.processPods( appIosDir: projectUnderTest, iosEngineDir: 'engine/path', pluginOrFlutterPodChanged: false ); verifyNever(mockProcessManager.run( ['pod', 'install', '--verbose'], workingDirectory: 'project/ios', environment: {'FLUTTER_FRAMEWORK_DIR': 'engine/path', 'COCOAPODS_DISABLE_STATS': 'true'}, )); }, overrides: { FileSystem: () => fs, ProcessManager: () => mockProcessManager, }, ); } class MockProcessManager extends Mock implements ProcessManager {} class TestCocoaPods extends CocoaPods { const TestCocoaPods([this._hasCocoaPods = true]); final bool _hasCocoaPods; @override Future get hasCocoaPods => new Future.value(_hasCocoaPods); @override Future get cocoaPodsVersionText async => new Future.value('1.5.0'); @override Future get isCocoaPodsInitialized => new Future.value(true); } final ProcessResult exitsHappy = new ProcessResult( 1, // pid 0, // exitCode '', // stdout '', // stderr );