Add app host support for test specs

parent 417503d9
......@@ -12,6 +12,10 @@ To install release candidates run `[sudo] gem install cocoapods --pre`
[Paul Beusterien](https://github.com/paulb777)
[#7044](https://github.com/CocoaPods/CocoaPods/pull/7044)
* Add app host support for test specs
[Dimitris Koutsogiorgas](https://github.com/dnkoutso)
[#6953](https://github.com/CocoaPods/CocoaPods/issues/6953)
##### Bug Fixes
* Fix framework and resources paths caching
......
......@@ -66,6 +66,7 @@ module Pod
autoload :PrefixHeader, 'cocoapods/generator/prefix_header'
autoload :UmbrellaHeader, 'cocoapods/generator/umbrella_header'
autoload :XCConfig, 'cocoapods/generator/xcconfig'
autoload :AppTargetHelper, 'cocoapods/generator/app_target_helper'
end
require 'cocoapods/core_overrides'
......
module Pod
module Generator
# Stores the common logic for creating app targets within projects including
# generating standard import and main files for app hosts.
#
module AppTargetHelper
# Adds a single app target to the given project with the provided name.
#
# @param [Project] project
# the Xcodeproj to generate the target into.
#
# @param [Symbol] platform
# the platform of the target. Can be `:ios` or `:osx`, etc.
#
# @param [String] deployment_target
# the deployment target for the platform.
#
# @param [String] name
# The name to use for the target, defaults to 'App'.
#
# @return [PBXNativeTarget] the new target that was created.
#
def self.add_app_target(project, platform, deployment_target, name = 'App')
project.new_target(:application, name, platform, deployment_target)
end
# Creates and links an import file for the given pod target and into the given native target.
#
# @param [Project] project
# the Xcodeproj to generate the target into.
#
# @param [PBXNativeTarget] target
# the native target to link the generated import file into.
#
# @param [PodTarget] pod_target
# the pod target to use for when generating the contents of the import file.
#
# @param [Symbol] platform
# the platform of the target. Can be `:ios` or `:osx`, etc.
#
# @param [Boolean] use_frameworks
# whether to use frameworks or not when generating the contents of the import file.
#
# @param [String] name
# The name to use for the target, defaults to 'App'.
#
# @return [Array<PBXBuildFile>] the created build file references.
#
def self.add_app_project_import(project, target, pod_target, platform, use_frameworks, name = 'App')
source_file = AppTargetHelper.create_app_import_source_file(project, pod_target, platform, use_frameworks, name)
source_file_ref = project.new_group(name, name).new_file(source_file)
target.add_file_references([source_file_ref])
end
# Creates and links a default app host 'main.m' file.
#
# @param [Project] project
# the Xcodeproj to generate the target into.
#
# @param [PBXNativeTarget] target
# the native target to link the generated main file into.
#
# @param [Symbol] platform
# the platform of the target. Can be `:ios` or `:osx`, etc.
#
# @param [String] name
# The name to use for the target, defaults to 'App'.
#
# @return [Array<PBXBuildFile>] the created build file references.
#
def self.add_app_host_main_file(project, target, platform, name = 'App')
source_file = AppTargetHelper.create_app_host_main_file(project, platform, name)
source_file_ref = project.new_group(name, name).new_file(source_file)
target.add_file_references([source_file_ref])
end
# Adds the xctest framework search paths into the given target.
#
# @param [PBXNativeTarget] target
# the native target to add XCTest into.
#
# @return [void]
#
def self.add_xctest_search_paths(target)
target.build_configurations.each do |configuration|
search_paths = configuration.build_settings['FRAMEWORK_SEARCH_PATHS'] ||= '$(inherited)'
search_paths << ' "$(PLATFORM_DIR)/Developer/Library/Frameworks"'
end
end
# Adds the provided swift version into the given target.
#
# @param [PBXNativeTarget] target
# the native target to add the swift version into.
#
# @param [String] swift_version
# the swift version to set to.
#
# @return [void]
#
def self.add_swift_version(target, swift_version)
target.build_configurations.each do |configuration|
configuration.build_settings['SWIFT_VERSION'] = swift_version
end
end
# Creates a default import file for the given pod target.
#
# @param [Project] project
# the Xcodeproj to generate the target into.
#
# @param [PodTarget] pod_target
# the pod target to use for when generating the contents of the import file.
#
# @param [Symbol] platform
# the platform of the target. Can be `:ios` or `:osx`, etc.
#
# @param [Boolean] use_frameworks
# whether to use frameworks or not when generating the contents of the import file.
#
# @param [String] name
# The name of the folder to use and save the generated main file.
#
# @return [Pathname] the new source file that was generated.
#
def self.create_app_import_source_file(project, pod_target, platform, use_frameworks, name = 'App')
language = pod_target.uses_swift? ? :swift : :objc
if language == :swift
source_file = project.path.dirname.+("#{name}/main.swift")
source_file.parent.mkpath
import_statement = use_frameworks && pod_target.should_build? ? "import #{pod_target.product_module_name}\n" : ''
source_file.open('w') { |f| f << import_statement }
else
source_file = project.path.dirname.+("#{name}/main.m")
source_file.parent.mkpath
import_statement = if use_frameworks && pod_target.should_build?
"@import #{pod_target.product_module_name};\n"
else
header_name = "#{pod_target.product_module_name}/#{pod_target.product_module_name}.h"
if pod_target.sandbox.public_headers.root.+(header_name).file?
"#import <#{header_name}>\n"
else
''
end
end
source_file.open('w') do |f|
f << "@import Foundation;\n"
f << "@import UIKit;\n" if platform == :ios || platform == :tvos
f << "@import Cocoa;\n" if platform == :osx
f << "#{import_statement}int main() {}\n"
end
end
source_file
end
# Creates a default app host 'main.m' file.
#
# @param [Project] project
# the Xcodeproj to generate the target into.
#
# @param [Symbol] platform
# the platform of the target. Can be `:ios` or `:osx`.
#
# @param [String] name
# The name of the folder to use and save the generated main file.
#
# @return [Pathname] the new source file that was generated.
#
def self.create_app_host_main_file(project, platform, name = 'App')
source_file = project.path.dirname.+("#{name}/main.m")
source_file.parent.mkpath
source_file.open('w') do |f|
case platform
when :ios, :tvos
f << IOS_APP_HOST_MAIN_CONTENTS
when :osx
f << MACOS_APP_APP_HOST_MAIN_CONTENTS
end
end
source_file
end
IOS_APP_HOST_MAIN_CONTENTS = <<EOS.freeze
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface CPTestAppHostAppDelegate : UIResponder <UIApplicationDelegate>
@property (nonatomic, strong) UIWindow *window;
@end
@implementation CPTestAppHostAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = [UIViewController new];
[self.window makeKeyAndVisible];
return YES;
}
@end
int main(int argc, char *argv[])
{
@autoreleasepool
{
return UIApplicationMain(argc, argv, nil, NSStringFromClass([CPTestAppHostAppDelegate class]));
}
}
EOS
MACOS_APP_APP_HOST_MAIN_CONTENTS = <<EOS.freeze
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
return NSApplicationMain(argc, argv);
}
EOS
end
end
end
......@@ -5,9 +5,13 @@ module Pod
# framework. It states public attributes.
#
class InfoPlistFile
# @return [Target] the target represented by this Info.plist.
# @return [Version] version The version to use for when generating this Info.plist file.
#
attr_reader :target
attr_reader :version
# @return [Platform] The platform to use for when generating this Info.plist file.
#
attr_reader :platform
# @return [Symbol] the CFBundlePackageType of the target this Info.plist
# file is for.
......@@ -16,12 +20,13 @@ module Pod
# Initialize a new instance
#
# @param [Target] target @see target
#
# @param [Version] version @see version
# @param [Platform] platform @see platform
# @param [Symbol] bundle_package_type @see bundle_package_type
#
def initialize(target, bundle_package_type: :fmwk)
@target = target
def initialize(version, platform, bundle_package_type = :fmwk)
@version = version
@platform = platform
@bundle_package_type = bundle_package_type
end
......@@ -39,21 +44,6 @@ module Pod
end
end
# The version associated with the current target
#
# @note Will return 1.0.0 for the AggregateTarget
#
# @return [String]
#
def target_version
if target && target.respond_to?(:root_spec)
version = target.root_spec.version
[version.major, version.minor, version.patch].join('.')
else
'1.0.0'
end
end
# Generates the contents of the Info.plist
#
# @return [String]
......@@ -108,7 +98,7 @@ module Pod
'CFBundleInfoDictionaryVersion' => '6.0',
'CFBundleName' => '${PRODUCT_NAME}',
'CFBundlePackageType' => bundle_package_type.to_s.upcase,
'CFBundleShortVersionString' => target_version,
'CFBundleShortVersionString' => version,
'CFBundleSignature' => '????',
'CFBundleVersion' => '${CURRENT_PROJECT_VERSION}',
'NSPrincipalClass' => '',
......@@ -117,6 +107,7 @@ module Pod
info['CFBundleExecutable'] = '${EXECUTABLE_NAME}' if bundle_package_type != :bndl
info['CFBundleVersion'] = '1' if bundle_package_type == :bndl
info['NSPrincipalClass'] = 'NSApplication' if bundle_package_type == :appl && platform == :osx
info
end
......
......@@ -287,11 +287,16 @@ module Pod
def add_pod_target_test_dependencies(pod_target, frameworks_group)
test_dependent_targets = pod_target.all_test_dependent_targets
pod_target.test_native_targets.each do |test_native_target|
pod_target.test_specs_by_native_target.each do |test_native_target, test_specs|
test_dependent_targets.reject(&:should_build?).each do |test_dependent_target|
add_resource_bundles_to_native_target(test_dependent_target, test_native_target)
end
add_dependent_targets_to_native_target(test_dependent_targets, test_native_target, false, pod_target.requires_frameworks?, frameworks_group)
test_spec_consumers = test_specs.map { |test_spec| test_spec.consumer(pod_target.platform) }
if test_spec_consumers.any?(&:requires_app_host?)
app_host_target = project.targets.find { |t| t.name == pod_target.app_host_label(test_specs.first.test_type) }
test_native_target.add_dependency(app_host_target)
end
end
end
......
......@@ -17,7 +17,7 @@ module Pod
create_support_files_group
create_xcconfig_file
if target.requires_frameworks?
create_info_plist_file
create_info_plist_file(target.info_plist_path, native_target, target.version, target.platform)
create_module_map
create_umbrella_header
end
......
......@@ -19,14 +19,17 @@ module Pod
UI.message "- Installing target `#{target.name}` #{target.platform}" do
add_target
create_support_files_dir
add_test_targets if target.contains_test_specifications?
if target.contains_test_specifications?
add_test_targets
add_test_app_host_targets
end
add_resources_bundle_targets
add_files_to_build_phases
create_xcconfig_file
create_test_xcconfig_files if target.contains_test_specifications?
if target.requires_frameworks?
unless target.static_framework?
create_info_plist_file
create_info_plist_file(target.info_plist_path, native_target, target.version, target.platform)
end
create_module_map
create_umbrella_header do |generator|
......@@ -180,6 +183,42 @@ module Pod
end
end
# Adds the test app host targets for the library to the Pods project with the
# appropriate build configurations.
#
# @return [void]
#
def add_test_app_host_targets
target.test_specs.each do |test_spec|
next unless test_spec.consumer(target.platform).requires_app_host?
name = target.app_host_label(test_spec.test_type)
platform_name = target.platform.name
app_host_target = project.targets.find { |t| t.name == name }
if app_host_target.nil?
app_host_target = Pod::Generator::AppTargetHelper.add_app_target(project, platform_name, deployment_target, name)
app_host_target.build_configurations.each do |configuration|
configuration.build_settings.merge!(custom_build_settings)
configuration.build_settings['PRODUCT_NAME'] = name
configuration.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = 'org.cocoapods.${PRODUCT_NAME:rfc1034identifier}'
end
Pod::Generator::AppTargetHelper.add_app_host_main_file(project, app_host_target, platform_name, name)
app_host_info_plist_path = project.path.dirname.+("#{name}/Info.plist")
create_info_plist_file(app_host_info_plist_path, app_host_target, '1.0.0', target.platform, :appl)
end
# Wire all test native targets with the app host.
native_test_target = target.native_target_for_spec(test_spec)
native_test_target.build_configurations.each do |configuration|
test_host = "$(BUILT_PRODUCTS_DIR)/#{name}.app/"
test_host << 'Contents/MacOS/' if target.platform == :osx
test_host << name.to_s
configuration.build_settings['TEST_HOST'] = test_host
end
target_attributes = project.root_object.attributes['TargetAttributes'] || {}
target_attributes[native_test_target.uuid.to_s] = { 'TestTargetID' => app_host_target.uuid.to_s }
project.root_object.attributes['TargetAttributes'] = target_attributes
end
end
# Adds the test targets for the library to the Pods project with the
# appropriate build configurations.
#
......@@ -267,7 +306,7 @@ module Pod
path = target.info_plist_path
path.dirname.mkdir unless path.dirname.exist?
info_plist_path = path.dirname + "ResourceBundle-#{bundle_name}-#{path.basename}"
generator = Generator::InfoPlistFile.new(target, :bundle_package_type => :bndl)
generator = Generator::InfoPlistFile.new(target.version, target.platform, :bndl)
update_changed_file(generator, info_plist_path)
add_file_to_support_group(info_plist_path)
......
......@@ -136,12 +136,26 @@ module Pod
# Creates the Info.plist file which sets public framework attributes
#
# @param [Pathname] path
# the path to save the generated Info.plist file.
#
# @param [PBXNativeTarget] native_target
# the native target to link the generated Info.plist file into.
#
# @param [Version] version
# the version to use for when generating this Info.plist file.
#
# @param [Platform] platform
# the platform to use for when generating this Info.plist file.
#
# @param [Symbol] bundle_package_type
# the CFBundlePackageType of the target this Info.plist file is for.
#
# @return [void]
#
def create_info_plist_file
path = target.info_plist_path
def create_info_plist_file(path, native_target, version, platform, bundle_package_type = :fmwk)
UI.message "- Generating Info.plist file at #{UI.path(path)}" do
generator = Generator::InfoPlistFile.new(target)
generator = Generator::InfoPlistFile.new(version, platform, bundle_package_type)
update_changed_file(generator, path)
add_file_to_support_group(path)
......
......@@ -6,6 +6,8 @@ module Pod
# This class is used to represent both the targets and their libraries.
#
class Target
DEFAULT_VERSION = '1.0.0'.freeze
# @return [Sandbox] The sandbox where the Pods should be installed.
#
attr_reader :sandbox
......@@ -180,6 +182,12 @@ module Pod
support_files_dir + "#{label}-dummy.m"
end
# @return [String] The version associated with this target
#
def version
DEFAULT_VERSION
end
#-------------------------------------------------------------------------#
private
......
......@@ -185,16 +185,29 @@ module Pod
end
end
# @return [Hash{Array => Specification}] a hash where the keys are the test native targets and the value
# an array of all the test specs associated with this native target.
#
def test_specs_by_native_target
test_specs.group_by { |test_spec| native_target_for_spec(test_spec) }
end
# @return [Boolean] Whether the target has any tests specifications.
#
def contains_test_specifications?
specs.any?(&:test_specification?)
end
# @return [Array<Specification>] All of the test specs within this target.
#
def test_specs
specs.select(&:test_specification?)
end
# @return [Array<Symbol>] All of the test supported types within this target.
#
def supported_test_types
specs.select(&:test_specification?).map(&:test_type).uniq
test_specs.map(&:test_type).uniq
end
# Returns the framework paths associated with this target. By default all paths include the framework paths
......@@ -344,6 +357,15 @@ module Pod
"#{label}-#{test_type.capitalize}-Tests"
end
# @param [Symbol] test_type
# The test type to use for producing the test label.
#
# @return [String] The label of the app host label to use given the platform and test type.
#
def app_host_label(test_type)
"AppHost-#{Platform.string_name(platform.symbolic_name)}-#{test_type.capitalize}-Tests"
end
# @param [Symbol] test_type
# The test type this embed frameworks script path is for.
#
......@@ -497,6 +519,13 @@ module Pod
"${PODS_ROOT}/#{sandbox.pod_dir(pod_name).relative_path_from(sandbox.root)}"
end
# @return [String] The version associated with this target
#
def version
version = root_spec.version
[version.major, version.minor, version.patch].join('.')
end
private
# @param [TargetDefinition] target_definition
......
......@@ -439,71 +439,24 @@ module Pod
def create_app_project
app_project = Xcodeproj::Project.new(validation_dir + 'App.xcodeproj')
app_project.new_target(:application, 'App', consumer.platform_name, deployment_target)
Pod::Generator::AppTargetHelper.add_app_target(app_project, consumer.platform_name, deployment_target)
app_project.save
app_project.recreate_user_schemes
end
def add_app_project_import
app_project = Xcodeproj::Project.open(validation_dir + 'App.xcodeproj')
pod_target = @installer.pod_targets.find { |pt| pt.pod_name == spec.root.name }
source_file = write_app_import_source_file(pod_target)
source_file_ref = app_project.new_group('App', 'App').new_file(source_file)
app_target = app_project.targets.first
app_target.add_file_references([source_file_ref])
add_swift_version(app_target)
add_xctest(app_target) if @installer.pod_targets.any? { |pt| pt.spec_consumers.any? { |c| c.frameworks.include?('XCTest') } }
pod_target = @installer.pod_targets.find { |pt| pt.pod_name == spec.root.name }
Pod::Generator::AppTargetHelper.add_app_project_import(app_project, app_target, pod_target, consumer.platform_name, use_frameworks)
Pod::Generator::AppTargetHelper.add_swift_version(app_target, swift_version)
Pod::Generator::AppTargetHelper.add_xctest_search_paths(app_target) if @installer.pod_targets.any? { |pt| pt.spec_consumers.any? { |c| c.frameworks.include?('XCTest') } }
app_project.save
Xcodeproj::XCScheme.share_scheme(app_project.path, 'App')
# Share the pods xcscheme only if it exists. For pre-built vendored pods there is no xcscheme generated.
Xcodeproj::XCScheme.share_scheme(@installer.pods_project.path, pod_target.label) if shares_pod_target_xcscheme?(pod_target)
end
def add_swift_version(app_target)
app_target.build_configurations.each do |configuration|
configuration.build_settings['SWIFT_VERSION'] = swift_version
end
end
def add_xctest(app_target)
app_target.build_configurations.each do |configuration|
search_paths = configuration.build_settings['FRAMEWORK_SEARCH_PATHS'] ||= '$(inherited)'
search_paths << ' "$(PLATFORM_DIR)/Developer/Library/Frameworks"'
end
end
def write_app_import_source_file(pod_target)
language = pod_target.uses_swift? ? :swift : :objc
if language == :swift
source_file = validation_dir.+('App/main.swift')
source_file.parent.mkpath
import_statement = use_frameworks && pod_target.should_build? ? "import #{pod_target.product_module_name}\n" : ''
source_file.open('w') { |f| f << import_statement }
else
source_file = validation_dir.+('App/main.m')
source_file.parent.mkpath
import_statement = if use_frameworks && pod_target.should_build?
"@import #{pod_target.product_module_name};\n"
else
header_name = "#{pod_target.product_module_name}/#{pod_target.product_module_name}.h"
if pod_target.sandbox.public_headers.root.+(header_name).file?
"#import <#{header_name}>\n"
else
''
end
end
source_file.open('w') do |f|
f << "@import Foundation;\n"
f << "@import UIKit;\n" if consumer.platform_name == :ios || consumer.platform_name == :tvos
f << "@import Cocoa;\n" if consumer.platform_name == :osx
f << "#{import_statement}int main() {}\n"
end
end
source_file
end
# It creates a podfile in memory and builds a library containing the pod
# for all available platforms with xcodebuild.
#
......
require File.expand_path('../../../spec_helper', __FILE__)
module Pod
module Generator
module AppTargetHelper
describe 'creating the import file' do
describe 'when linting as a framework' do
it 'creates a swift import' do
pod_target = stub(:uses_swift? => true, :should_build? => true, :product_module_name => 'ModuleName', :name => 'ModuleName')
project = stub(:path => Pathname(Dir.mktmpdir(['CocoaPods-Lint-', "-#{pod_target.name}"])) + 'App.xcodeproj')
file = AppTargetHelper.create_app_import_source_file(project, pod_target, :ios, true)
file.basename.to_s.should == 'main.swift'
file.read.should == <<-SWIFT.strip_heredoc
import ModuleName
SWIFT
end
it 'creates an objective-c import' do
pod_target = stub(:uses_swift? => false, :should_build? => true, :product_module_name => 'ModuleName', :name => 'ModuleName')
project = stub(:path => Pathname(Dir.mktmpdir(['CocoaPods-Lint-', "-#{pod_target.name}"])) + 'App.xcodeproj')
file = AppTargetHelper.create_app_import_source_file(project, pod_target, :ios, true)
file.basename.to_s.should == 'main.m'
file.read.should == <<-OBJC.strip_heredoc
@import Foundation;
@import UIKit;
@import ModuleName;
int main() {}
OBJC
end
it 'creates no import when the pod target has no source files' do
pod_target = stub(:uses_swift? => true, :should_build? => false, :name => 'ModuleName')
project = stub(:path => Pathname(Dir.mktmpdir(['CocoaPods-Lint-', "-#{pod_target.name}"])) + 'App.xcodeproj')
file = AppTargetHelper.create_app_import_source_file(project, pod_target, :ios, true)
file.basename.to_s.should == 'main.swift'
file.read.should == ''
end
end
describe 'when linting as a static lib' do
before do
@sandbox = config.sandbox
end
it 'creates an objective-c import when a plausible umbrella header is found' do
pod_target = stub(:uses_swift? => false, :should_build? => true, :product_module_name => 'ModuleName', :name => 'ModuleName', :sandbox => @sandbox)
project = stub(:path => Pathname(Dir.mktmpdir(['CocoaPods-Lint-', "-#{pod_target.name}"])) + 'App.xcodeproj')
header_name = "#{pod_target.product_module_name}/#{pod_target.product_module_name}.h"
umbrella = pod_target.sandbox.public_headers.root.+(header_name)
umbrella.dirname.mkpath
umbrella.open('w') {}
file = AppTargetHelper.create_app_import_source_file(project, pod_target, :ios, false)
file.basename.to_s.should == 'main.m'
file.read.should == <<-OBJC.strip_heredoc
@import Foundation;
@import UIKit;
#import <ModuleName/ModuleName.h>
int main() {}
OBJC
end
it 'does not create an objective-c import when no umbrella header is found' do
pod_target = stub(:uses_swift? => false, :should_build? => true, :product_module_name => 'ModuleName', :name => 'ModuleName', :sandbox => @sandbox)
project = stub(:path => Pathname(Dir.mktmpdir(['CocoaPods-Lint-', "-#{pod_target.name}"])) + 'App.xcodeproj')
file = AppTargetHelper.create_app_import_source_file(project, pod_target, :ios, false)
file.basename.to_s.should == 'main.m'
file.read.should == <<-OBJC.strip_heredoc
@import Foundation;
@import UIKit;
int main() {}
OBJC
end
end
end
describe 'creating an app host main file' do
it 'creates correct main file for iOS' do
pod_target = stub(:uses_swift? => false, :should_build? => true, :product_module_name => 'ModuleName', :name => 'ModuleName', :sandbox => @sandbox)
project = stub(:path => Pathname(Dir.mktmpdir(['CocoaPods-Lint-', "-#{pod_target.name}"])) + 'App.xcodeproj')
file = AppTargetHelper.create_app_host_main_file(project, :ios)
file.basename.to_s.should == 'main.m'
file.read.should == AppTargetHelper::IOS_APP_HOST_MAIN_CONTENTS
end
it 'creates correct main file for macOS' do
pod_target = stub(:uses_swift? => false, :should_build? => true, :product_module_name => 'ModuleName', :name => 'ModuleName', :sandbox => @sandbox)
project = stub(:path => Pathname(Dir.mktmpdir(['CocoaPods-Lint-', "-#{pod_target.name}"])) + 'App.xcodeproj')
file = AppTargetHelper.create_app_host_main_file(project, :osx)
file.basename.to_s.should == 'main.m'
file.read.should == AppTargetHelper::MACOS_APP_APP_HOST_MAIN_CONTENTS
end
end
end
end
end
......@@ -2,56 +2,13 @@ require File.expand_path('../../../spec_helper', __FILE__)
module Pod
describe Generator::InfoPlistFile do
describe '#target_version' do
it 'returns 1.0.0 for the aggregate target' do
generator = Generator::InfoPlistFile.new(fixture_aggregate_target)
generator.target_version.should == '1.0.0'
end
describe 'sanitization' do
before do
@root_spec = mock('RootSpec')
@platform = stub('Platform', :name => :ios)
pod_target = stub('PodTarget', :root_spec => @root_spec, :platform => @platform)
@generator = Generator::InfoPlistFile.new(pod_target)
end
it 'handles when the version is more than 3 numeric parts' do
version = Version.new('0.2.0.1')
@root_spec.stubs(:version).returns(version)
@generator.target_version.should == '0.2.0'
end
it 'handles when the version is less than 3 numeric parts' do
version = Version.new('0.2')
@root_spec.stubs(:version).returns(version)
@generator.target_version.should == '0.2.0'
end
it 'handles when the version is a pre-release' do
version = Version.new('1.0.0-beta.1')
@root_spec.stubs(:version).returns(version)
@generator.target_version.should == '1.0.0'
version = Version.new('1.0-beta.5')
@root_spec.stubs(:version).returns(version)
@generator.target_version.should == '1.0.0'
end
end
it 'returns the specification\'s version for the pod target' do
generator = Generator::InfoPlistFile.new(fixture_pod_target('orange-framework/OrangeFramework.podspec'))
generator.target_version.should == '0.1.0'
end
end
it 'replaces the version in the generated plist' do
generator = Generator::InfoPlistFile.new(fixture_pod_target('orange-framework/OrangeFramework.podspec'))
generator = Generator::InfoPlistFile.new('0.1.0', Platform.new(:ios, '6.0'))
generator.generate.should.include "<key>CFBundleShortVersionString</key>\n <string>0.1.0</string>"
end
it 'generates a valid Info.plist file' do
generator = Generator::InfoPlistFile.new(fixture_pod_target('orange-framework/OrangeFramework.podspec'))
generator = Generator::InfoPlistFile.new('1.0.0', Platform.new(:ios, '6.0'))
file = temporary_directory + 'Info.plist'
generator.save_as(file)
`plutil -lint #{file}`
......@@ -59,7 +16,7 @@ module Pod
end if Executable.which('plutil')
it 'generates a correct Info.plist file' do
generator = Generator::InfoPlistFile.new(mock('Target'))
generator = Generator::InfoPlistFile.new('1.0.0', Platform.new(:ios, '6.0'))
file = temporary_directory + 'Info.plist'
generator.save_as(file)
Xcodeproj::Plist.read_from_path(file).should == {
......@@ -74,10 +31,46 @@ module Pod
'CFBundleVersion' => '${CURRENT_PROJECT_VERSION}',
'NSPrincipalClass' => '',
}
it 'sets the package type' do
generator = Generator::InfoPlistFile.new('1.0.0', Platform.new(:ios, '6.0'), :appl)
file = temporary_directory + 'Info.plist'
generator.save_as(file)
Xcodeproj::Plist.read_from_path(file).should == {
'CFBundleDevelopmentRegion' => 'en',
'CFBundleExecutable' => '${EXECUTABLE_NAME}',
'CFBundleIdentifier' => '${PRODUCT_BUNDLE_IDENTIFIER}',
'CFBundleInfoDictionaryVersion' => '6.0',
'CFBundleName' => '${PRODUCT_NAME}',
'CFBundlePackageType' => 'APPL',
'CFBundleShortVersionString' => '1.0.0',
'CFBundleSignature' => '????',
'CFBundleVersion' => '${CURRENT_PROJECT_VERSION}',
'NSPrincipalClass' => '',
}
end
it 'adds NSPrincipalClass for OSX platform' do
generator = Generator::InfoPlistFile.new('1.0.0', Platform.new(:osx, '10.8'), :appl)
file = temporary_directory + 'Info.plist'
generator.save_as(file)
Xcodeproj::Plist.read_from_path(file).should == {
'CFBundleDevelopmentRegion' => 'en',
'CFBundleExecutable' => '${EXECUTABLE_NAME}',
'CFBundleIdentifier' => '${PRODUCT_BUNDLE_IDENTIFIER}',
'CFBundleInfoDictionaryVersion' => '6.0',
'CFBundleName' => '${PRODUCT_NAME}',
'CFBundlePackageType' => 'APPL',
'CFBundleShortVersionString' => '1.0.0',
'CFBundleSignature' => '????',
'CFBundleVersion' => '${CURRENT_PROJECT_VERSION}',
'NSPrincipalClass' => 'NSApplication',
}
end
end
it 'properly formats serialized arrays' do
generator = Generator::InfoPlistFile.new(mock('Target'))
generator = Generator::InfoPlistFile.new('1.0.0', Platform.new(:ios, '6.0'))
generator.send(:to_plist, 'array' => %w(a b)).should == <<-PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
......@@ -94,16 +87,14 @@ module Pod
end
it 'uses the specified bundle_package_type' do
target = mock('Target')
generator = Generator::InfoPlistFile.new(target, :bundle_package_type => :bndl)
generator = Generator::InfoPlistFile.new('1.0.0', Platform.new(:ios, '6.0'), :bndl)
file = temporary_directory + 'Info.plist'
generator.save_as(file)
Xcodeproj::Plist.read_from_path(file)['CFBundlePackageType'].should == 'BNDL'
end
it 'does not include a CFBundleExecutable for bundles' do
target = mock('Target')
generator = Generator::InfoPlistFile.new(target, :bundle_package_type => :bndl)
generator = Generator::InfoPlistFile.new('1.0.0', Platform.new(:ios, '6.0'), :bndl)
file = temporary_directory + 'Info.plist'
generator.save_as(file)
Xcodeproj::Plist.read_from_path(file).should.not.key('CFBundleExecutable')
......
......@@ -74,7 +74,7 @@ module Pod
it "adds the user's build configurations to the target" do
@pod_target.user_build_configurations.merge!('AppStore' => :release, 'Test' => :debug)
@installer.install!
@project.targets.first.build_configurations.map(&:name).sort.should == %w( AppStore Debug Release Test )
@project.targets.first.build_configurations.map(&:name).sort.should == %w(AppStore Debug Release Test)
end
it 'it creates different hash instances for the build settings of various build configurations' do
......@@ -274,6 +274,50 @@ module Pod
bc.build_settings['CONFIGURATION_BUILD_DIR'].should.be.nil
end
end
describe 'app host generation' do
before do
@coconut_spec.test_specs.first.requires_app_host = true
end
it 'creates and links app host with an iOS test native target' do
@installer.install!
@project.targets.count.should == 3
app_host_target = @project.targets[2]
app_host_target.name.should == 'AppHost-iOS-Unit-Tests'
app_host_target.symbol_type.should == :application
native_test_target = @project.targets[1]
native_test_target.build_configurations.each do |bc|
bc.build_settings['TEST_HOST'].should == '$(BUILT_PRODUCTS_DIR)/AppHost-iOS-Unit-Tests.app/AppHost-iOS-Unit-Tests'
end
@project.root_object.attributes['TargetAttributes'].should == {
native_test_target.uuid.to_s => {
'TestTargetID' => app_host_target.uuid.to_s,
},
}
end
it 'creates and links app host with an OSX test native target' do
@installer2.install!
@project.targets.count.should == 3
app_host_target = @project.targets[2]
app_host_target.name.should == 'AppHost-macOS-Unit-Tests'
app_host_target.symbol_type.should == :application
app_host_target.build_configurations.each do |bc|
bc.build_settings['PRODUCT_NAME'].should == 'AppHost-macOS-Unit-Tests'
bc.build_settings['PRODUCT_BUNDLE_IDENTIFIER'].should == 'org.cocoapods.${PRODUCT_NAME:rfc1034identifier}'
end
native_test_target = @project.targets[1]
native_test_target.build_configurations.each do |bc|
bc.build_settings['TEST_HOST'].should == '$(BUILT_PRODUCTS_DIR)/AppHost-macOS-Unit-Tests.app/Contents/MacOS/AppHost-macOS-Unit-Tests'
end
@project.root_object.attributes['TargetAttributes'].should == {
native_test_target.uuid.to_s => {
'TestTargetID' => app_host_target.uuid.to_s,
},
}
end
end
end
describe 'test other files under sources' do
......
......@@ -269,7 +269,7 @@ module Pod
it 'adds all test dependent targets to test native targets' do
mock_native_target = mock('CoconutLib')
mock_test_native_target = mock('CoconutLib-Unit-Tests')
mock_test_native_target = mock('CoconutLib-Unit-Tests', :symbol_type => :unit_test_bundle)
dependent_native_target = mock('DependentNativeTarget')
test_dependent_native_target = mock('TestDependentNativeTarget')
......@@ -302,6 +302,7 @@ module Pod
@target.stubs(:pod_targets).returns([])
@generator.expects(:pod_targets).returns([@pod_target])
mock_native_target = mock('CoconutLib')
mock_test_native_target = mock('CoconutLib-Unit-Tests', :symbol_type => :unit_test_bundle)
dependent_native_target = mock('DependentNativeTarget')
dependent_target = mock('dependent-target', :dependent_targets => [])
......@@ -309,18 +310,20 @@ module Pod
dependent_target.stubs(:native_target).returns(dependent_native_target)
@pod_target.stubs(:native_target).returns(mock_native_target)
@pod_target.stubs(:test_native_targets).returns([])
@pod_target.stubs(:test_native_targets).returns([mock_test_native_target])
@pod_target.stubs(:dependent_targets).returns([dependent_target])
@pod_target.stubs(:test_dependent_targets).returns([])
@pod_target.stubs(:should_build? => true)
mock_native_target.expects(:add_dependency).with(dependent_native_target)
mock_test_native_target.expects(:add_dependency).with(dependent_native_target)
mock_test_native_target.expects(:add_dependency).with(mock_native_target)
@generator.send(:set_target_dependencies)
end
it 'adds test dependencies to test native targets for a pod target that should not be built' do
mock_test_native_target = mock('CoconutLib-Unit-Tests')
mock_test_native_target = mock('CoconutLib-Unit-Tests', :symbol_type => :unit_test_bundle)
test_dependent_native_target = mock('TestDependentNativeTarget')
test_dependent_target = mock('dependent-test-target', :should_build? => true, :native_target => test_dependent_native_target)
test_dependent_target.expects(:should_build?).returns(true)
......@@ -335,7 +338,7 @@ module Pod
end
it 'sets resource bundles for not build pods as target dependencies of the test target' do
mock_test_native_target = mock('CoconutLib-Unit-Tests')
mock_test_native_target = mock('CoconutLib-Unit-Tests', :symbol_type => :unit_test_bundle)
@pod_target.stubs(:test_native_targets).returns([mock_test_native_target])
@pod_target.stubs(:test_dependent_targets).returns([])
......@@ -347,6 +350,26 @@ module Pod
@generator.send(:set_target_dependencies)
end
it 'sets the app host dependency target to the test native target if test spec requires app host' do
mock_app_host_target = mock(:name => 'AppHost-iOS-Unit-Tests')
@generator.project.stubs(:targets).returns([mock_app_host_target])
mock_test_native_target = mock('CoconutLib-Unit-Tests', :symbol_type => :unit_test_bundle)
test_dependent_native_target = mock('TestDependentNativeTarget')
test_dependent_target = mock('dependent-test-target', :should_build? => true, :native_target => test_dependent_native_target)
test_dependent_target.expects(:should_build?).returns(true)
@pod_target.test_specs.first.requires_app_host = true
@pod_target.stubs(:test_native_targets).returns([mock_test_native_target])
@pod_target.stubs(:all_test_dependent_targets).returns([test_dependent_target])
@pod_target.stubs(:should_build? => false)
mock_test_native_target.expects(:add_dependency).with(test_dependent_native_target)
mock_test_native_target.expects(:add_dependency).with(mock_app_host_target)
@generator.send(:set_target_dependencies)
end
end
#--------------------------------------#
......
......@@ -111,6 +111,30 @@ module Pod
end
end
describe 'target version' do
it 'handles when the version is more than 3 numeric parts' do
version = Version.new('0.2.0.1')
@pod_target.root_spec.stubs(:version).returns(version)
@pod_target.version.should == '0.2.0'
end
it 'handles when the version is less than 3 numeric parts' do
version = Version.new('0.2')
@pod_target.root_spec.stubs(:version).returns(version)
@pod_target.version.should == '0.2.0'
end
it 'handles when the version is a pre-release' do
version = Version.new('1.0.0-beta.1')
@pod_target.root_spec.stubs(:version).returns(version)
@pod_target.version.should == '1.0.0'
version = Version.new('1.0-beta.5')
@pod_target.root_spec.stubs(:version).returns(version)
@pod_target.version.should == '1.0.0'
end
end
describe 'Support files' do
it 'returns the absolute path of the xcconfig file' do
@pod_target.xcconfig_path('Release').to_s.should.include?(
......@@ -315,7 +339,7 @@ module Pod
@test_spec_target_definition = Podfile::TargetDefinition.new('Pods', nil)
@test_spec_target_definition.abstract = false
@test_pod_target = PodTarget.new([@coconut_spec, *@coconut_spec.recursive_subspecs], [@test_spec_target_definition], config.sandbox)
@test_pod_target.stubs(:platform).returns(:ios)
@test_pod_target.stubs(:platform).returns(Platform.new(:ios, '6.0'))
end
it 'returns that it has test specifications' do
......@@ -330,6 +354,10 @@ module Pod
@test_pod_target.test_target_label(:unit).should == 'CoconutLib-Unit-Tests'
end
it 'returns app host label based on test type' do
@test_pod_target.app_host_label(:unit).should == 'AppHost-iOS-Unit-Tests'
end
it 'returns the correct native target based on the consumer provided' do
@test_pod_target.stubs(:native_target).returns(stub(:name => 'CoconutLib', :symbol_type => :dynamic_library, :product_reference => stub(:name => 'libCoconutLib.a')))
@test_pod_target.stubs(:test_native_targets).returns([stub(:name => 'CoconutLib-Unit-Tests', :symbol_type => :unit_test_bundle, :product_reference => stub(:name => 'CoconutLib-Unit-Tests'))])
......
......@@ -581,81 +581,6 @@ module Pod
Xcodeproj::Project.schemes(project.path).should == %w(App)
end
describe 'creating the importing file' do
describe 'when linting as a framework' do
before do
@validator.stubs(:use_frameworks).returns(true)
end
it 'creates a swift import' do
pod_target = stub(:uses_swift? => true, :should_build? => true, :product_module_name => 'ModuleName')
file = @validator.send(:write_app_import_source_file, pod_target)
file.basename.to_s.should == 'main.swift'
file.read.should == <<-SWIFT.strip_heredoc
import ModuleName
SWIFT
end
it 'creates an objective-c import' do
pod_target = stub(:uses_swift? => false, :should_build? => true, :product_module_name => 'ModuleName')
file = @validator.send(:write_app_import_source_file, pod_target)
file.basename.to_s.should == 'main.m'
file.read.should == <<-OBJC.strip_heredoc
@import Foundation;
@import UIKit;
@import ModuleName;
int main() {}
OBJC
end
it 'creates no import when the pod target has no source files' do
pod_target = stub(:uses_swift? => true, :should_build? => false)
file = @validator.send(:write_app_import_source_file, pod_target)
file.basename.to_s.should == 'main.swift'
file.read.should == ''
end
end
describe 'when linting as a static lib' do
before do
@validator.stubs(:use_frameworks).returns(false)
@sandbox = config.sandbox
end
it 'creates an objective-c import when a plausible umbrella header is found' do
pod_target = stub(:uses_swift? => false, :should_build? => true, :product_module_name => 'ModuleName', :sandbox => @sandbox)
header_name = "#{pod_target.product_module_name}/#{pod_target.product_module_name}.h"
umbrella = pod_target.sandbox.public_headers.root.+(header_name)
umbrella.dirname.mkpath
umbrella.open('w') {}
file = @validator.send(:write_app_import_source_file, pod_target)
file.basename.to_s.should == 'main.m'
file.read.should == <<-OBJC.strip_heredoc
@import Foundation;
@import UIKit;
#import <ModuleName/ModuleName.h>
int main() {}
OBJC
end
it 'does not create an objective-c import when no umbrella header is found' do
pod_target = stub(:uses_swift? => false, :should_build? => true, :product_module_name => 'ModuleName', :sandbox => @sandbox)
file = @validator.send(:write_app_import_source_file, pod_target)
file.basename.to_s.should == 'main.m'
file.read.should == <<-OBJC.strip_heredoc
@import Foundation;
@import UIKit;
int main() {}
OBJC
end
end
end
it 'adds the importing file to the app target' do
@validator.stubs(:use_frameworks).returns(true)
@validator.send(:create_app_project)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment