Skip to content

Commit

Permalink
Merge pull request #420 from calabash/feature/improve-installed-app-c…
Browse files Browse the repository at this point in the history
…heck-for-xcode-7-sims

Improve installed app check for Xcode 7 simulators
  • Loading branch information
jmoody committed Mar 24, 2016
2 parents 206f760 + fc50791 commit 68b5953
Show file tree
Hide file tree
Showing 21 changed files with 282 additions and 151 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

### 2.1.0

This release fixes a bug that might put the iOS Simulator into a bad state.
An iOS Simulator could potentially have a .app installed in its directory
structure, but the app would not appear in Springboard or be detected by
simctl. We have fixed the bug for Xcode 7. There is no possible fix for
Xcode 6. In order to ensure that your iOS Simulators are in a good shape we
recommend that all users run:

```
# Will take ~10 minutes depending on the number of installed simulators.
#
# Don't forget to run this on your CI machines.
#
# You only have to run this command once!
$ DEBUG=1 run-loop simctl doctor
```

We apologize for the inconvenience.

* Core.run\_with\_options improve the way AUT and DUT are inferred #414
* Core.detecti\_uia\_strategy given options and RunLoop::Device #413
* DetectAUT.detect\_app\_under\_test #412
Expand Down
2 changes: 1 addition & 1 deletion lib/run_loop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
require 'run_loop/host_cache'
require 'run_loop/patches/awesome_print'
require 'run_loop/core_simulator'
require 'run_loop/simctl/plists'
require "run_loop/simctl"
require 'run_loop/template'
require "run_loop/locale"
require "run_loop/language"
Expand Down
11 changes: 8 additions & 3 deletions lib/run_loop/core_simulator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -744,15 +744,20 @@ def same_sha1_as_installed?
def installed_app_bundle_dir
sim_app_dir = device_applications_dir
return nil if !File.exist?(sim_app_dir)
Dir.glob("#{sim_app_dir}/**/*.app").find do |path|
RunLoop::App.new(path).bundle_identifier == app.bundle_identifier

if xcode.version_gte_7?
simctl = RunLoop::Simctl.new(device)
simctl.app_container(app.bundle_identifier)
else
Dir.glob("#{sim_app_dir}/**/*.app").find do |path|
RunLoop::App.new(path).bundle_identifier == app.bundle_identifier
end
end
end

# 1. Does nothing if the app is not installed.
# 2. Does nothing if the app the same as the app that is installed
# 3. Installs app if it is different from the installed app
#
def ensure_app_same
installed_app_bundle = installed_app_bundle_dir

Expand Down
89 changes: 89 additions & 0 deletions lib/run_loop/simctl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
module RunLoop

# @!visibility private
# An interface to the `simctl` command line tool for CoreSimulator.
#
# Replacement for SimControl.
class Simctl

# @!visibility private
DEFAULTS = {
:timeout => RunLoop::Environment.ci? ? 90 : 30,
:log_cmd => true
}

# @!visibility private
SIMCTL_PLIST_DIR = lambda {
dirname = File.dirname(__FILE__)
joined = File.join(dirname, '..', '..', 'plists', 'simctl')
File.expand_path(joined)
}.call

# @!visibility private
def self.uia_automation_plist
File.join(SIMCTL_PLIST_DIR, 'com.apple.UIAutomation.plist')
end

# @!visibility private
def self.uia_automation_plugin_plist
File.join(SIMCTL_PLIST_DIR, 'com.apple.UIAutomationPlugIn.plist')
end

# @!visibility private
attr_accessor :device

# @!visibility private
#
# @param [RunLoop::Device] device Cannot be nil.
def initialize(device)
@device = device
end

# @!visibility private
def to_s
"#<Simctl: #{device.name} #{device.udid}>"
end

# @!visibility private
def inspect
to_s
end

# @!visibility private
#
# This method is not supported on Xcode < 7 - returns nil.
#
# @param [String] bundle_id The CFBundleIdentifier of the app.
# @return [String] The path to the .app bundle if it exists; nil otherwise.
def app_container(bundle_id)
return nil if !xcode.version_gte_7?
cmd = ["simctl", "get_app_container", device.udid, bundle_id]
hash = execute(cmd, DEFAULTS)

exit_status = hash[:exit_status]
if exit_status != 0
nil
else
hash[:out].strip
end
end

private

# @!visibility private
def execute(array, options)
merged = DEFAULTS.merge(options)
xcrun.exec(array, merged)
end

# @!visibility private
def xcrun
@xcrun ||= RunLoop::Xcrun.new
end

# @!visibility private
def xcode
@xcode ||= RunLoop::Xcode.new
end
end
end
20 changes: 0 additions & 20 deletions lib/run_loop/simctl/plists.rb

This file was deleted.

153 changes: 77 additions & 76 deletions spec/lib/core_simulator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@
let(:device) { RunLoop::Device.new('iPhone 5s', '8.1',
'A08334BE-77BD-4A2F-BA25-A0E8251A1A80') }
let(:core_sim) { RunLoop::CoreSimulator.new(device, app) }
let(:simctl) { RunLoop::Simctl.new(device) }
let(:xcode) { RunLoop::Xcode.new }

describe '#uninstall_app_and_sandbox' do
it 'does nothing if the app is not installed' do
Expand Down Expand Up @@ -436,6 +438,70 @@
end
end

describe "#installed_app_bundle_dir" do
let(:app_dir) do
path = File.join(Resources.shared.local_tmp_dir, "Containers/Bundle/Application")
FileUtils.mkdir_p(path)
path
end

let(:bundle_id) { app.bundle_identifier }

before do
expect(core_sim).to receive(:device_applications_dir).and_return(app_dir)
allow(core_sim).to receive(:xcode).and_return(xcode)
end

it "device applications dir does not exist" do
expect(File).to receive(:exist?).with(app_dir).and_return(false)

expect(core_sim.send(:installed_app_bundle_dir)).to be == nil
end

describe "Xcode >= 7" do

before do
expect(xcode).to receive(:version_gte_7?).and_return(true)
end

it "app installed" do
expect(RunLoop::Simctl).to receive(:new).with(device).and_return(simctl)
expect(simctl).to receive(:app_container).with(bundle_id).and_return(:path)

expect(core_sim.send(:installed_app_bundle_dir)).to be == :path
end

it "app not installed" do
expect(RunLoop::Simctl).to receive(:new).with(device).and_return(simctl)
expect(simctl).to receive(:app_container).with(bundle_id).and_return(nil)

expect(core_sim.send(:installed_app_bundle_dir)).to be == nil
end
end

describe "Xcode < 7" do
let(:glob) { "#{app_dir}/**/*.app" }

before do
expect(xcode).to receive(:version_gte_7?).and_return(false)
end

it "app not installed" do
expect(Dir).to receive(:glob).with(glob).and_return([])

expect(core_sim.send(:installed_app_bundle_dir)).to be == nil
end

it "app is installed" do
FileUtils.cp_r(app.path, app_dir)

actual = core_sim.send(:installed_app_bundle_dir)
expected = "#{app_dir}/#{File.basename(app.path)}"
expect(actual).to be == expected
end
end
end

describe '#app_sandbox_dir' do
it 'returns nil if there is no app bundle' do
expect(core_sim).to receive(:installed_app_bundle_dir).and_return nil
Expand Down Expand Up @@ -552,19 +618,10 @@
RunLoop::Device.new('iPhone 5s', '8.4', '6386C48A-E029-4C1A-932D-355F652F66B9')
end

let(:sdk_71_device_with_app) do
RunLoop::Device.new('iPhone 5s', '7.1', 'B88A172B-CF92-4D3A-8A88-96FF4A6303D3')
end

let(:sdk_71_device_without_app) do
RunLoop::Device.new('iPhone 5', '7.1', '8DE4DF9B-09A4-4CFF-88E1-C62C88DD1503')
end

before do
source = File.join(Resources.shared.resources_dir, 'CoreSimulator')
FileUtils.cp_r(source, tmp_dir)
stub_const('RunLoop::CoreSimulator::CORE_SIMULATOR_DEVICE_DIR',
directory)
stub_const('RunLoop::CoreSimulator::CORE_SIMULATOR_DEVICE_DIR', directory)
end

# Not yet.
Expand All @@ -586,77 +643,21 @@
# expect(logs.count).to be == 2
# end


describe '#installed_app_bundle_dir' do
describe 'iOS >= 8' do
it 'app is installed' do
core_sim = RunLoop::CoreSimulator.new(device_with_app, app)

actual = core_sim.send(:installed_app_bundle_dir)
expect(actual).to be_truthy
expect(File.exist?(actual)).to be_truthy
end

it 'app is not installed' do
core_sim = RunLoop::CoreSimulator.new(device_without_app, app)

actual = core_sim.send(:installed_app_bundle_dir)
expect(actual).to be_falsey
end
end

describe 'iOS < 8' do
it 'app is installed' do
core_sim = RunLoop::CoreSimulator.new(sdk_71_device_with_app, app)

actual = core_sim.send(:installed_app_bundle_dir)
expect(actual).to be_truthy
expect(File.exist?(actual)).to be_truthy
end

it 'app is not installed' do
core_sim = RunLoop::CoreSimulator.new(sdk_71_device_without_app, app)

actual = core_sim.send(:installed_app_bundle_dir)
expect(actual).to be_falsey
end
end
end

describe '#app_sandbox_dir' do
it 'app is installed' do
core_sim = RunLoop::CoreSimulator.new(device_with_app, app)
expect(core_sim).to receive(:installed_app_bundle_dir).and_return("path/My.app")

describe 'iOS >= 8' do
it 'app is installed' do
core_sim = RunLoop::CoreSimulator.new(device_with_app, app)

actual = core_sim.send(:app_sandbox_dir)
expect(actual).to be_truthy
expect(File.exist?(actual)).to be_truthy
end

it 'app is not installed' do
core_sim = RunLoop::CoreSimulator.new(device_without_app, app)

actual = core_sim.send(:app_sandbox_dir)
expect(actual).to be_falsey
end
actual = core_sim.send(:app_sandbox_dir)
expect(actual).to be_truthy
expect(File.exist?(actual)).to be_truthy
end

describe 'iOS < 8' do
it 'app is installed' do
core_sim = RunLoop::CoreSimulator.new(sdk_71_device_with_app, app)

actual = core_sim.send(:app_sandbox_dir)
expect(actual).to be_truthy
expect(File.exist?(actual)).to be_truthy
end

it 'app is not installed' do
core_sim = RunLoop::CoreSimulator.new(sdk_71_device_without_app, app)
it 'app is not installed' do
core_sim = RunLoop::CoreSimulator.new(device_without_app, app)

actual = core_sim.send(:app_sandbox_dir)
expect(actual).to be_falsey
end
actual = core_sim.send(:app_sandbox_dir)
expect(actual).to be_falsey
end
end

Expand Down
16 changes: 0 additions & 16 deletions spec/lib/simctl/plists_spec.rb

This file was deleted.

Loading

0 comments on commit 68b5953

Please sign in to comment.