Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SimLauncher: fix autodetection of app bundle #974

Merged
105 changes: 62 additions & 43 deletions calabash-cucumber/lib/calabash-cucumber/launch/simulator_launcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -241,52 +241,32 @@ def app_bundle_or_raise(path=nil, device_build_dir='iPhoneSimulator')
msg << "Make sure you build for Simulator and that you're using the Calabash components"
raise msg.join("\n")
end
if full_console_logging?
puts('-'*37)
puts "Auto detected APP_BUNDLE_PATH:\n\n"

puts "APP_BUNDLE_PATH= '#{bundle_path}'\n\n"
puts 'Please verify!'
puts "If this is wrong please set it as APP_BUNDLE_PATH in features/support/01_launch.rb\n"
puts('-'*37)
end
else
bo_dir = build_output_dir_for_project
sim_dirs = Dir.glob(File.join(bo_dir, '*-iphonesimulator', '*.app'))

if sim_dirs.empty?
msg = ['Unable to auto detect APP_BUNDLE_PATH.']
msg << 'Have you built your app for simulator?'
msg << "Searched dir: #{bo_dir}"
msg << 'Please build your app from Xcode'
msg << 'You should build the -cal target.'
msg << ''
msg << 'Alternatively, specify APP_BUNDLE_PATH in features/support/01_launch.rb'
msg << "This should point to the location of your built app linked with calabash.\n"
raise msg.join("\n")
end
preferred_dir = find_preferred_dir(sim_dirs)
if preferred_dir.nil?
msg = ['Error... Unable to find APP_BUNDLE_PATH.']
msg << 'Cannot find a built app that is linked with calabash.framework'
msg << 'Please build your app from Xcode'
msg << 'You should build your calabash target.'
msg << ''
msg << 'Alternatively, specify APP_BUNDLE_PATH in features/support/01_launch.rb'
msg << "This should point to the location of your built app linked with calabash.\n"
raise msg.join("\n")
end
if full_console_logging?
puts('-'*37)
puts "Auto detected APP_BUNDLE_PATH:\n\n"
search_dir = build_output_dir_for_project || DERIVED_DATA
bundle_path = select_most_recent_bundle(search_dir)

if bundle_path.nil?
raise RuntimeError,
%Q{
Unable to auto detect a .app that is linked Calabash.

puts "APP_BUNDLE_PATH=#{preferred_dir || sim_dirs[0]}\n\n"
puts 'Please verify!'
puts "If this is wrong please set it as APP_BUNDLE_PATH in features/support/01_launch.rb\n"
puts('-'*37)
Searched: #{search_dir}

Have you built your app for simulator?

Please build your app from Xcode

Alternatively, specify APP in features/support/01_launch.rb
or as an environment variable:

$ APP=/path/to/Your.app bundle exec cucumber
}
end
bundle_path = sim_dirs[0]
end

Calabash::Cucumber.log_debug("Auto detected app at path:")
Calabash::Cucumber.log_debug(bundle_path)
Calabash::Cucumber.log_debug("If this is incorrect, set the APP variable")
bundle_path
end

Expand Down Expand Up @@ -592,7 +572,46 @@ def version_check(version)
_deprecated('0.9.169', 'check is now done in Launcher', :warn)
raise(NotImplementedError, 'this method has been deprecated and will be removed')
end
end

private

# @!visibility private
def app(bundle_path)
RunLoop::App.new(bundle_path)
end

# @!visibility private
def lipo(bundle_path)
RunLoop::Lipo.new(bundle_path)
end

# @!visibility private
def collect_app_bundles(base_dir)
bundles = []

Dir.glob("#{base_dir}/**/*.app") do |bundle|
next if !RunLoop::App.valid?(bundle)

lipo = lipo(bundle)
arches = lipo.info
if arches.include?("x86_64") || arches.include?("i386")
app = app(bundle)
if app.calabash_server_version
bundles << bundle
end
end
end

bundles
end

# @!visibility private
def select_most_recent_bundle(base_dir)
bundles = collect_app_bundles(base_dir)
bundles.max do |a, b|
File.mtime(a) <=> File.mtime(b)
end
end
end
end
end
143 changes: 143 additions & 0 deletions calabash-cucumber/spec/lib/launch/simulator_launcher_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@

describe Calabash::Cucumber::SimulatorLauncher do
let(:base_dir) { File.join("tmp", "bundle-detection") }
let(:a) { File.join(base_dir, "A.app") }
let(:b) { File.join(base_dir, "B.app") }
let(:c) { File.join(base_dir, "C.app") }
let(:d) { File.join(base_dir, "D.app") }
let(:launcher) { Calabash::Cucumber::SimulatorLauncher.new }

before do
base_dir = File.join("tmp", "bundle-detection")
FileUtils.rm_rf(base_dir)
FileUtils.mkdir_p(base_dir)

make_bundle = lambda do |bundle_path|
FileUtils.mkdir_p(bundle_path)
name = File.basename(bundle_path).split(".")[0]
FileUtils.touch(File.join(bundle_path, name))

pbuddy = RunLoop::PlistBuddy.new
info_plist = File.join(bundle_path, "Info.plist")
pbuddy.create_plist(info_plist)
bundle_id = "com.example.#{name}"
pbuddy.plist_set("CFBundleIdentifier", "string", bundle_id, info_plist)
pbuddy.plist_set("CFBundleExecutable", "string", name, info_plist)
end

make_bundle.call(a)
make_bundle.call(b)
make_bundle.call(c)
make_bundle.call(d)
end

describe "#collect_app_bundles" do
let(:app_a) { RunLoop::App.new(a) }
let(:app_b) { RunLoop::App.new(b) }
let(:app_c) { RunLoop::App.new(c) }
let(:app_d) { RunLoop::App.new(d) }

let(:lipo_a) { RunLoop::Lipo.new(a) }
let(:lipo_b) { RunLoop::Lipo.new(b) }
let(:lipo_c) { RunLoop::Lipo.new(c) }
let(:lipo_d) { RunLoop::Lipo.new(d) }

let(:v8) { RunLoop::Version.new("8.0") }
let(:v6) { RunLoop::Version.new("6.0") }

let(:i386) { ["i386"] }
let(:x86_64) { ["x86_64"] }
let(:fat) { i386 + x86_64 }

before do
allow(launcher).to receive(:app).with(a).and_return(app_a)
allow(launcher).to receive(:app).with(b).and_return(app_b)
allow(launcher).to receive(:app).with(c).and_return(app_c)
allow(launcher).to receive(:app).with(d).and_return(app_d)

allow(launcher).to receive(:lipo).with(a).and_return(lipo_a)
allow(launcher).to receive(:lipo).with(b).and_return(lipo_b)
allow(launcher).to receive(:lipo).with(c).and_return(lipo_c)
allow(launcher).to receive(:lipo).with(d).and_return(lipo_d)
end

it "collects valid bundles" do
expect(app_a).to receive(:calabash_server_version).and_return(v8)
expect(app_b).to receive(:calabash_server_version).and_return(v8)
expect(app_c).to receive(:calabash_server_version).and_return(v8)
expect(app_d).to receive(:calabash_server_version).and_return(v8)

expect(lipo_a).to receive(:info).and_return(fat)
expect(lipo_b).to receive(:info).and_return(i386)
expect(lipo_c).to receive(:info).and_return(x86_64)
expect(lipo_d).to receive(:info).and_return(x86_64)

actual = launcher.send(:collect_app_bundles, base_dir)
expected = [a, b, c, d]
expect(actual).to be == expected
end

it "rejects invalid bundles" do
expect(RunLoop::App).to receive(:valid?).with(a).and_return false
expect(RunLoop::App).to receive(:valid?).with(b).and_return false
expect(RunLoop::App).to receive(:valid?).with(c).and_return false
expect(RunLoop::App).to receive(:valid?).with(d).and_return false

actual = launcher.send(:collect_app_bundles, base_dir)
expect(actual).to be == []
end

it "rejects arm bundles" do
expect(app_b).to receive(:calabash_server_version).and_return(v8)
expect(app_c).to receive(:calabash_server_version).and_return(v8)

expect(lipo_a).to receive(:info).and_return(["arm64"])
expect(lipo_b).to receive(:info).and_return(i386)
expect(lipo_c).to receive(:info).and_return(x86_64)
expect(lipo_d).to receive(:info).and_return(["armv7"])

actual = launcher.send(:collect_app_bundles, base_dir)
expected = [b, c]
expect(actual).to be == expected
end

it "rejects bundles without calabash server" do
expect(app_a).to receive(:calabash_server_version).and_return(v8)
expect(app_b).to receive(:calabash_server_version).and_return(nil)
expect(app_c).to receive(:calabash_server_version).and_return(nil)
expect(app_d).to receive(:calabash_server_version).and_return(v8)

expect(lipo_a).to receive(:info).and_return(fat)
expect(lipo_b).to receive(:info).and_return(i386)
expect(lipo_c).to receive(:info).and_return(x86_64)
expect(lipo_d).to receive(:info).and_return(x86_64)

actual = launcher.send(:collect_app_bundles, base_dir)
expected = [a, d]
expect(actual).to be == expected
end
end

describe "#select_most_recent_bundle" do
it "returns nil if no bundles are collected" do
expect(launcher).to receive(:collect_app_bundles).with(base_dir).and_return([])

actual = launcher.send(:select_most_recent_bundle, base_dir)
expect(actual).to be == nil
end

it "returns the most recently modified" do
array = [a, b, c, d]
expect(File).to receive(:mtime).with(a).at_least(:once).and_return(0)
expect(File).to receive(:mtime).with(b).at_least(:once).and_return(1)
expect(File).to receive(:mtime).with(c).at_least(:once).and_return(2)
expect(File).to receive(:mtime).with(d).at_least(:once).and_return(3)

expect(launcher).to receive(:collect_app_bundles).with(base_dir).and_return(array)

actual = launcher.send(:select_most_recent_bundle, base_dir)
expect(actual).to be == d
end
end
end