run_loop 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 37ad2fc838d709c0132334cf1a24b88822664e9b
4
- data.tar.gz: 818e85d9a7480820bdc7737cf99ed1d42b3aef1a
3
+ metadata.gz: 66f671a88ff045375fc68e725a7718dd22a99769
4
+ data.tar.gz: 1c9ea08167d06e4a168af8b14a731a5448ea16ca
5
5
  SHA512:
6
- metadata.gz: 12670d8f9a3490d292280e03adaf8d74343ed5af14568e257d8333a2314cb4602c4f566dd0ea55318daaaf1d26e7f207a3db10c0e8d8486bf701eefbab8ac8aa
7
- data.tar.gz: 7fff1abf53e185e59ef0b92c8b851b59c0581300ce5d1f1a851b3e0bfeaad2a26df3dbc476141b1deff84ff4a4974e14102f7f44c869e82ba1c2de695cad05be
6
+ metadata.gz: f4e9da22b7d00d2228663a0009decf562004e5def1e68245179fef574c9e9e0ea94375e92473cd37007fb13d6c477cce06e1346caa56dd7350871fed6e3b3ebb
7
+ data.tar.gz: 39095dd3bbe9f8b6b67bf3e51c081568513ad7dcdcde681332faec200bbd9bb527fa19c17959172744285a4e7bf539cd7bab028696ea0644bf64db466ea68e68
data/lib/run_loop.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'run_loop/directory'
1
2
  require 'run_loop/environment'
2
3
  require 'run_loop/logging'
3
4
  require 'run_loop/process_terminator'
@@ -18,6 +19,7 @@ require 'run_loop/host_cache'
18
19
  require 'run_loop/patches/awesome_print'
19
20
  require 'run_loop/patches/retriable'
20
21
  require 'run_loop/simctl/bridge'
22
+ require 'run_loop/simctl/plists'
21
23
 
22
24
  module RunLoop
23
25
 
@@ -2,6 +2,7 @@ require 'thor'
2
2
  require 'run_loop'
3
3
  require 'run_loop/cli/errors'
4
4
  require 'run_loop/cli/instruments'
5
+ require 'run_loop/cli/simctl'
5
6
 
6
7
  trap 'SIGINT' do
7
8
  puts 'Trapped SIGINT - exiting'
@@ -27,6 +28,9 @@ module RunLoop
27
28
  desc 'instruments', "Interact with Xcode's command-line instruments"
28
29
  subcommand 'instruments', RunLoop::CLI::Instruments
29
30
 
31
+ desc 'simctl', "Interact with Xcode's command-line simctl"
32
+ subcommand 'simctl', RunLoop::CLI::Simctl
33
+
30
34
  end
31
35
  end
32
36
  end
@@ -8,7 +8,7 @@ module RunLoop
8
8
 
9
9
  attr_accessor :signal
10
10
 
11
- desc 'instruments quit', 'Send a kill signal to all instruments processes.'
11
+ desc 'quit', 'Send a kill signal to all instruments processes.'
12
12
 
13
13
  method_option 'signal',
14
14
  :desc => 'The kill signal to send.',
@@ -39,7 +39,7 @@ module RunLoop
39
39
  end
40
40
 
41
41
 
42
- desc 'instruments launch [--app | [--ipa | --bundle-id]] [OPTIONS]', 'Launch an app with instruments.'
42
+ desc 'launch [--app | [--ipa | --bundle-id]] [OPTIONS]', 'Launch an app with instruments.'
43
43
 
44
44
  # This is the description we want, but Thor doesn't handle newlines well(?).
45
45
  # long_desc <<EOF
@@ -0,0 +1,200 @@
1
+ require 'thor'
2
+ require 'run_loop'
3
+ require 'run_loop/cli/errors'
4
+
5
+ module RunLoop
6
+ module CLI
7
+ class Simctl < Thor
8
+
9
+ attr_reader :sim_control
10
+
11
+ desc 'tail', 'Tail the log file of the booted simulator'
12
+ def tail
13
+ tail_booted
14
+ end
15
+
16
+ no_commands do
17
+ def tail_booted
18
+ device = booted_device
19
+ log_file = device.simulator_log_file_path
20
+ exec('tail', *['-F', log_file])
21
+ end
22
+ end
23
+
24
+ desc 'booted', 'Prints details about the booted simulator'
25
+ def booted
26
+ device = booted_device
27
+ if device.nil?
28
+ puts 'No simulator is booted.'
29
+ else
30
+ puts device
31
+ end
32
+ end
33
+
34
+ no_commands do
35
+ def sim_control
36
+ @sim_control ||= RunLoop::SimControl.new
37
+ end
38
+
39
+ def booted_device
40
+ sim_control.simulators.detect(nil) do |device|
41
+ device.state == 'Booted'
42
+ end
43
+ end
44
+ end
45
+
46
+ desc 'install --app [OPTIONS]', 'Installs an app on a device'
47
+
48
+ method_option 'app',
49
+ :desc => 'Path to a .app bundle to launch on simulator.',
50
+ :aliases => '-a',
51
+ :required => true,
52
+ :type => :string
53
+
54
+ method_option 'device',
55
+ :desc => 'The device UDID or simulator identifier.',
56
+ :aliases => '-d',
57
+ :required => false,
58
+ :type => :string
59
+
60
+ method_option 'force',
61
+ :desc => 'Force a re-install the existing app.',
62
+ :aliases => '-f',
63
+ :required => false,
64
+ :default => false,
65
+ :type => :boolean
66
+
67
+ method_option 'debug',
68
+ :desc => 'Enable debug logging.',
69
+ :aliases => '-v',
70
+ :required => false,
71
+ :default => false,
72
+ :type => :boolean
73
+
74
+ def install
75
+ debug = options[:debug]
76
+
77
+ if debug
78
+ ENV['DEBUG'] = '1'
79
+ end
80
+
81
+ debug_logging = RunLoop::Environment.debug?
82
+
83
+ device = expect_device(options)
84
+ app = expect_app(options, device)
85
+
86
+ bridge = RunLoop::Simctl::Bridge.new(device, app.path)
87
+
88
+ force_reinstall = options[:force]
89
+
90
+ before = Time.now
91
+
92
+ if bridge.app_is_installed?
93
+ if debug_logging
94
+ puts "App with bundle id '#{app.bundle_identifier}' is already installed."
95
+ end
96
+
97
+ if force_reinstall
98
+ if debug_logging
99
+ puts 'Will force a re-install.'
100
+ end
101
+ bridge.uninstall
102
+ bridge.install
103
+ else
104
+ new_digest = RunLoop::Directory.directory_digest(app.path)
105
+ if debug_logging
106
+ puts " New app has SHA: '#{new_digest}'."
107
+ end
108
+ installed_app_bundle = bridge.fetch_app_dir
109
+ old_digest = RunLoop::Directory.directory_digest(installed_app_bundle)
110
+ if debug_logging
111
+ puts "Installed app has SHA: '#{old_digest}'."
112
+ end
113
+ if new_digest != old_digest
114
+ if debug_logging
115
+ puts "Will re-install '#{app.bundle_identifier}' because the SHAs don't match."
116
+ end
117
+ bridge.uninstall
118
+ bridge.install
119
+ else
120
+ if debug_logging
121
+ puts "Will not re-install '#{app.bundle_identifier}' because the SHAs match."
122
+ end
123
+ end
124
+ end
125
+ else
126
+ bridge.install
127
+ end
128
+
129
+ if debug_logging
130
+ "Launching took #{Time.now-before} seconds"
131
+ puts "Installed '#{app.bundle_identifier}' on #{device} in #{Time.now-before} seconds."
132
+ end
133
+ end
134
+
135
+ no_commands do
136
+ def expect_device(options)
137
+ device_from_options = options[:device]
138
+ simulators = sim_control.simulators
139
+ if device_from_options.nil?
140
+ default_name = RunLoop::Core.default_simulator
141
+ device = simulators.detect do |sim|
142
+ sim.instruments_identifier == default_name
143
+ end
144
+
145
+ if device.nil?
146
+ raise RunLoop::CLI::ValidationError,
147
+ "Could not find a simulator with name that matches '#{device_from_options}'"
148
+ end
149
+ else
150
+ device = simulators.detect do |sim|
151
+ sim.udid == device_from_options ||
152
+ sim.instruments_identifier == device_from_options
153
+ end
154
+
155
+ if device.nil?
156
+ raise RunLoop::CLI::ValidationError,
157
+ "Could not find a simulator with name or UDID that matches '#{device_from_options}'"
158
+ end
159
+ end
160
+ device
161
+ end
162
+
163
+ def expect_app(options, device_obj)
164
+ app_bundle_path = options[:app]
165
+ unless File.exist?(app_bundle_path)
166
+ raise RunLoop::CLI::ValidationError, "Expected '#{app_bundle_path}' to exist."
167
+ end
168
+
169
+ unless File.directory?(app_bundle_path)
170
+ raise RunLoop::CLI::ValidationError,
171
+ "Expected '#{app_bundle_path}' to be a directory."
172
+ end
173
+
174
+ unless File.extname(app_bundle_path) == '.app'
175
+ raise RunLoop::CLI::ValidationError,
176
+ "Expected '#{app_bundle_path}' to end in .app."
177
+ end
178
+
179
+ app = RunLoop::App.new(app_bundle_path)
180
+
181
+ begin
182
+ app.bundle_identifier
183
+ app.executable_name
184
+ rescue RuntimeError => e
185
+ raise RunLoop::CLI::ValidationError, e.message
186
+ end
187
+
188
+ lipo = RunLoop::Lipo.new(app.path)
189
+ begin
190
+ lipo.expect_compatible_arch(device_obj)
191
+ rescue RunLoop::IncompatibleArchitecture => e
192
+ raise RunLoop::CLI::ValidationError, e.message
193
+ end
194
+
195
+ app
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
data/lib/run_loop/core.rb CHANGED
@@ -391,7 +391,9 @@ module RunLoop
391
391
  # @param [RunLoop::XCTools] xcode_tools Used to detect the current xcode
392
392
  # version.
393
393
  def self.default_simulator(xcode_tools=RunLoop::XCTools.new)
394
- if xcode_tools.xcode_version_gte_63?
394
+ if xcode_tools.xcode_version_gte_64?
395
+ 'iPhone 5s (8.4 Simulator)'
396
+ elsif xcode_tools.xcode_version_gte_63?
395
397
  'iPhone 5s (8.3 Simulator)'
396
398
  elsif xcode_tools.xcode_version_gte_62?
397
399
  'iPhone 5s (8.2 Simulator)'
@@ -8,6 +8,7 @@ module RunLoop
8
8
  attr_reader :simulator_root_dir
9
9
  attr_reader :simulator_accessibility_plist_path
10
10
  attr_reader :simulator_preferences_plist_path
11
+ attr_reader :simulator_log_file_path
11
12
 
12
13
  # Create a new device.
13
14
  #
@@ -32,6 +33,10 @@ module RunLoop
32
33
  end
33
34
  end
34
35
 
36
+ def to_s
37
+ "#{instruments_identifier} #{udid} #{instruction_set}"
38
+ end
39
+
35
40
  # Returns and instruments-ready device identifier that is a suitable value
36
41
  # for DEVICE_TARGET environment variable.
37
42
  #
@@ -111,8 +116,16 @@ module RunLoop
111
116
  }.call
112
117
  end
113
118
 
119
+ def simulator_log_file_path
120
+ @simulator_log_file_path ||= lambda {
121
+ return nil if physical_device?
122
+ File.join(CORE_SIMULATOR_LOGS_DIR, udid, 'system.log')
123
+ }.call
124
+ end
125
+
114
126
  private
115
127
 
116
128
  CORE_SIMULATOR_DEVICE_DIR = File.expand_path('~/Library/Developer/CoreSimulator/Devices')
129
+ CORE_SIMULATOR_LOGS_DIR = File.expand_path('~/Library/Logs/CoreSimulator')
117
130
  end
118
131
  end
@@ -0,0 +1,51 @@
1
+ require 'digest'
2
+ require 'openssl'
3
+
4
+ module RunLoop
5
+
6
+ # Class for performing operations on directories.
7
+ class Directory
8
+
9
+ # Dir.glob ignores files that start with '.', but we often need to find
10
+ # dotted files and directories.
11
+ #
12
+ # Ruby 2.* does the right thing by ignoring '..' and '.'.
13
+ #
14
+ # Ruby < 2.0 includes '..' and '.' in results which causes problems for some
15
+ # of run-loop's internal methods. In particular `reset_app_sandbox`.
16
+ def self.recursive_glob_for_entries(base_dir)
17
+ Dir.glob("#{base_dir}/{**/.*,**/*}").select do |entry|
18
+ !(entry.end_with?('..') || entry.end_with?('.'))
19
+ end
20
+ end
21
+
22
+ # Computes the digest of directory.
23
+ #
24
+ # @param path A path to a directory.
25
+ # @raise ArgumentError When `path` is not a directory or path does not exist.
26
+ def self.directory_digest(path)
27
+
28
+ unless File.exist?(path)
29
+ raise ArgumentError, "Expected '#{path}' to exist"
30
+ end
31
+
32
+ unless File.directory?(path)
33
+ raise ArgumentError, "Expected '#{path}' to be a directory"
34
+ end
35
+
36
+ entries = self.recursive_glob_for_entries(path)
37
+
38
+ if entries.empty?
39
+ raise ArgumentError, "Expected a non-empty dir at '#{path}' found '#{entries}'"
40
+ end
41
+
42
+ sha = OpenSSL::Digest::SHA256.new
43
+ self.recursive_glob_for_entries(path).each do |file|
44
+ unless File.directory?(file)
45
+ sha << File.read(file)
46
+ end
47
+ end
48
+ sha.hexdigest
49
+ end
50
+ end
51
+ end
data/lib/run_loop/lipo.rb CHANGED
@@ -71,7 +71,7 @@ module RunLoop
71
71
  raise RunLoop::IncompatibleArchitecture,
72
72
  ['Binary at:',
73
73
  binary_path,
74
- "does not contain a compatible architecture for target device.",
74
+ 'does not contain a compatible architecture for target device.',
75
75
  "Expected '#{instruction_set}' but found #{arches}."].join("\n")
76
76
  end
77
77
  end
@@ -85,9 +85,9 @@ module RunLoop
85
85
  output = stdout.read.strip
86
86
  begin
87
87
  output.split(':')[-1].strip.split
88
- rescue Exception => e
88
+ rescue StandardError => e
89
89
  msg = ['Expected to be able to parse the output of lipo.',
90
- "cmd: 'lipo -info #{escaped_binary_path}'",
90
+ "cmd: 'lipo -info \"#{binary_path}\"'",
91
91
  "stdout: '#{output}'",
92
92
  "stderr: '#{stderr.read.strip}'",
93
93
  "exit code: '#{wait_thr.value}'",
@@ -102,6 +102,7 @@ module RunLoop
102
102
  # Caller is responsible for correctly escaping arguments.
103
103
  # For example, the caller must proper quote `"` paths to avoid errors
104
104
  # when dealing with paths that contain spaces.
105
+ # @todo #execute_lipo should take an [] of arguments
105
106
  def execute_lipo(argument)
106
107
  command = "xcrun lipo #{argument}"
107
108
  Open3.popen3(command) do |_, stdout, stderr, wait_thr|
@@ -1,4 +1,5 @@
1
1
  require 'retriable'
2
+ require 'retriable/version'
2
3
 
3
4
  module RunLoop
4
5
  # A class to bridge the gap between retriable 1.x and 2.0.
@@ -22,3 +23,23 @@ module RunLoop
22
23
  end
23
24
  end
24
25
  end
26
+
27
+ # Only in retriable 1.4.0
28
+ unless Retriable.public_instance_methods.include?(:retriable)
29
+ require 'retriable/retry'
30
+ module Retriable
31
+ extend self
32
+
33
+ def retriable(opts = {}, &block)
34
+ raise LocalJumpError unless block_given?
35
+
36
+ Retry.new do |r|
37
+ r.tries = opts[:tries] if opts[:tries]
38
+ r.on = opts[:on] if opts[:on]
39
+ r.interval = opts[:interval] if opts[:interval]
40
+ r.timeout = opts[:timeout] if opts[:timeout]
41
+ r.on_retry = opts[:on_retry] if opts[:on_retry]
42
+ end.perform(&block)
43
+ end
44
+ end
45
+ end
@@ -1,3 +1,7 @@
1
+ require 'fileutils'
2
+ require 'open3'
3
+ #require 'retriable'
4
+
1
5
  module RunLoop::Simctl
2
6
 
3
7
  class SimctlError < StandardError
@@ -9,15 +13,19 @@ module RunLoop::Simctl
9
13
  #
10
14
  # TODO Some code is duplicated from sim_control.rb
11
15
  # TODO Reinstall if checksum does not match.
12
- # TODO Analyze terminate_core_simulator_processes
16
+ # TODO Analyze terminate_core_simulator_processes.
17
+ # TODO Figure out when CoreSimulator appears and does not appear.
13
18
  class Bridge
14
19
 
15
20
  attr_reader :device
16
21
  attr_reader :app
17
22
  attr_reader :sim_control
23
+ attr_reader :pbuddy
18
24
 
19
25
  def initialize(device, app_bundle_path)
20
26
 
27
+ @pbuddy = RunLoop::PlistBuddy.new
28
+
21
29
  @sim_control = RunLoop::SimControl.new
22
30
  @path_to_ios_sim_app_bundle = lambda {
23
31
  dev_dir = @sim_control.xctools.xcode_developer_dir
@@ -42,24 +50,96 @@ module RunLoop::Simctl
42
50
  terminate_core_simulator_processes
43
51
  end
44
52
 
45
- def simulator_app_dir
53
+ # @!visibility private
54
+ def is_sdk_8?
55
+ @is_sdk_8 ||= device.version >= RunLoop::Version.new('8.0')
56
+ end
57
+
58
+ def device_data_dir
59
+ @device_data_dir ||= File.join(CORE_SIMULATOR_DEVICE_DIR, device.udid, 'data')
60
+ end
61
+
62
+ def device_applications_dir
46
63
  @simulator_app_dir ||= lambda {
47
- device_dir = File.expand_path('~/Library/Developer/CoreSimulator/Devices')
48
- if device.version < RunLoop::Version.new('8.0')
49
- File.join(device_dir, device.udid, 'data', 'Applications')
64
+ if is_sdk_8?
65
+ File.join(device_data_dir, 'Containers', 'Bundle', 'Application')
50
66
  else
51
- File.join(device_dir, device.udid, 'data', 'Containers', 'Bundle', 'Application')
67
+ File.join(device_data_dir, 'Applications')
52
68
  end
53
69
  }.call
54
70
  end
55
71
 
72
+ def app_data_dir
73
+ app_install_dir = fetch_app_dir
74
+ return nil if app_install_dir.nil?
75
+ if is_sdk_8?
76
+ containers_data_dir = File.join(device_data_dir, 'Containers', 'Data', 'Application')
77
+ apps = Dir.glob("#{containers_data_dir}/**/#{METADATA_PLIST}")
78
+ match = apps.detect do |metadata_plist|
79
+ pbuddy.plist_read('MCMMetadataIdentifier', metadata_plist) == app.bundle_identifier
80
+ end
81
+ if match
82
+ File.dirname(match)
83
+ else
84
+ nil
85
+ end
86
+ else
87
+ app_install_dir
88
+ end
89
+ end
90
+
91
+ def app_library_dir
92
+ base_dir = app_data_dir
93
+ if base_dir.nil?
94
+ nil
95
+ else
96
+ File.join(base_dir, 'Library')
97
+ end
98
+ end
99
+
100
+ def app_library_preferences_dir
101
+ base_dir = app_library_dir
102
+ if base_dir.nil?
103
+ nil
104
+ else
105
+ File.join(base_dir, 'Preferences')
106
+ end
107
+ end
108
+
109
+ def app_documents_dir
110
+ base_dir = app_data_dir
111
+ if base_dir.nil?
112
+ nil
113
+ else
114
+ File.join(base_dir, 'Documents')
115
+ end
116
+ end
117
+
118
+ def app_tmp_dir
119
+ base_dir = app_data_dir
120
+ if base_dir.nil?
121
+ nil
122
+ else
123
+ File.join(base_dir, 'tmp')
124
+ end
125
+ end
126
+
127
+ def reset_app_sandbox
128
+ return true if !app_is_installed?
129
+
130
+ shutdown
131
+
132
+ reset_app_sandbox_internal
133
+ end
134
+
56
135
  def update_device_state(options={})
57
136
  merged_options = UPDATE_DEVICE_STATE_OPTS.merge(options)
58
- debug_logging = RunLoop::Environment.debug?
59
137
 
60
138
  interval = merged_options[:interval]
61
139
  tries = merged_options[:tries]
62
140
 
141
+ debug_logging = RunLoop::Environment.debug?
142
+
63
143
  on_retry = Proc.new do |_, try, elapsed_time, next_interval|
64
144
  if debug_logging
65
145
  # Retriable 2.0
@@ -95,13 +175,30 @@ module RunLoop::Simctl
95
175
  end
96
176
 
97
177
  def terminate_core_simulator_processes
98
- ['SimulatorBridge', 'CoreSimulatorBridge', 'configd_sim', 'launchd_sim'].each do |name|
178
+ debug_logging = RunLoop::Environment.debug?
179
+ [
180
+ # Probably no.
181
+ #'com.apple.CoreSimulator.CoreSimulatorService',
182
+ #'com.apple.CoreSimulator.SimVerificationService',
183
+
184
+ # Yes.
185
+ 'SimulatorBridge',
186
+ 'configd_sim',
187
+ 'launchd_sim',
188
+
189
+ # Yes, but does not always appear.
190
+ 'CoreSimulatorBridge'
191
+ ].each do |name|
99
192
  pids = RunLoop::ProcessWaiter.new(name).pids
100
193
  pids.each do |pid|
101
- puts "Sending 'TERM' to #{name} '#{pid}'"
194
+ if debug_logging
195
+ puts "Sending 'TERM' to #{name} '#{pid}'"
196
+ end
102
197
  term = RunLoop::ProcessTerminator.new(pid, 'TERM', name)
103
198
  unless term.kill_process
104
- puts "Sending 'KILL' to #{name} '#{pid}'"
199
+ if debug_logging
200
+ puts "Sending 'KILL' to #{name} '#{pid}'"
201
+ end
105
202
  term = RunLoop::ProcessTerminator.new(pid, 'KILL', name)
106
203
  term.kill_process
107
204
  end
@@ -123,7 +220,10 @@ module RunLoop::Simctl
123
220
  sleep delay
124
221
  end
125
222
 
126
- puts "Waited for #{timeout} seconds for device to have state: '#{target_state}'."
223
+ if RunLoop::Environment.debug?
224
+ puts "Waited for #{timeout} seconds for device to have state: '#{target_state}'."
225
+ end
226
+
127
227
  unless in_state
128
228
  raise "Expected '#{target_state} but found '#{device.state}' after waiting."
129
229
  end
@@ -131,13 +231,16 @@ module RunLoop::Simctl
131
231
  end
132
232
 
133
233
  def app_is_installed?
134
- sim_app_dir = simulator_app_dir
135
- return false if !File.exist?(sim_app_dir)
136
- app_path = Dir.glob("#{sim_app_dir}/**/*.app").detect do |path|
234
+ !fetch_app_dir.nil?
235
+ end
236
+
237
+ # @!visibility private
238
+ def fetch_app_dir
239
+ sim_app_dir = device_applications_dir
240
+ return nil if !File.exist?(sim_app_dir)
241
+ Dir.glob("#{sim_app_dir}/**/*.app").detect(nil) do |path|
137
242
  RunLoop::App.new(path).bundle_identifier == app.bundle_identifier
138
243
  end
139
-
140
- !app_path.nil?
141
244
  end
142
245
 
143
246
  def wait_for_app_install
@@ -154,7 +257,9 @@ module RunLoop::Simctl
154
257
  sleep delay
155
258
  end
156
259
 
157
- puts "Waited for #{timeout} seconds for '#{app.bundle_identifier}' to install."
260
+ if RunLoop::Environment.debug?
261
+ puts "Waited for #{timeout} seconds for '#{app.bundle_identifier}' to install."
262
+ end
158
263
 
159
264
  unless is_installed
160
265
  raise "Expected app to be installed on #{device.instruments_identifier}"
@@ -177,7 +282,9 @@ module RunLoop::Simctl
177
282
  sleep delay
178
283
  end
179
284
 
180
- puts "Waited for #{timeout} seconds for '#{app.bundle_identifier}' to uninstall."
285
+ if RunLoop::Environment.debug?
286
+ puts "Waited for #{timeout} seconds for '#{app.bundle_identifier}' to uninstall."
287
+ end
181
288
 
182
289
  unless not_installed
183
290
  raise "Expected app to be installed on #{device.instruments_identifier}"
@@ -262,7 +369,9 @@ module RunLoop::Simctl
262
369
  args = ['open', '-a', @path_to_ios_sim_app_bundle, '--args', '-CurrentDeviceUDID', device.udid]
263
370
  pid = spawn('xcrun', *args)
264
371
  Process.detach(pid)
265
- RunLoop::ProcessWaiter.new('CoreSimulatorBridge', WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
372
+
373
+ # @todo Does not always appear?
374
+ # RunLoop::ProcessWaiter.new('CoreSimulatorBridge', WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
266
375
  RunLoop::ProcessWaiter.new('iOS Simulator', WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
267
376
  RunLoop::ProcessWaiter.new('SimulatorBridge', WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
268
377
  wait_for_device_state 'Booted'
@@ -315,11 +424,95 @@ module RunLoop::Simctl
315
424
 
316
425
  SIM_POST_LAUNCH_WAIT = RunLoop::Environment.sim_post_launch_wait || 1.0
317
426
 
427
+ METADATA_PLIST = '.com.apple.mobile_container_manager.metadata.plist'
428
+ CORE_SIMULATOR_DEVICE_DIR = File.expand_path('~/Library/Developer/CoreSimulator/Devices')
429
+
318
430
  # @!visibility private
319
431
  def fetch_matching_device
320
432
  sim_control.simulators.detect do |sim|
321
433
  sim.udid == device.udid
322
434
  end
323
435
  end
436
+
437
+ # @!visibility private
438
+ def reset_app_sandbox_internal_shared
439
+ [app_documents_dir, app_tmp_dir].each do |dir|
440
+ FileUtils.rm_rf dir
441
+ FileUtils.mkdir dir
442
+ end
443
+ end
444
+
445
+ # @!visibility private
446
+ def reset_app_sandbox_internal_sdk_gte_8
447
+ lib_dir = app_library_dir
448
+ RunLoop::Directory.recursive_glob_for_entries(lib_dir).each do |entry|
449
+ if entry.include?('Preferences')
450
+ # nop
451
+ else
452
+ if File.exist?(entry)
453
+ FileUtils.rm_rf(entry)
454
+ end
455
+ end
456
+ end
457
+
458
+ prefs_dir = app_library_preferences_dir
459
+ protected = ['com.apple.UIAutomation.plist',
460
+ 'com.apple.UIAutomationPlugIn.plist']
461
+ RunLoop::Directory.recursive_glob_for_entries(prefs_dir).each do |entry|
462
+ unless protected.include?(File.basename(entry))
463
+ if File.exist?(entry)
464
+ FileUtils.rm_rf entry
465
+ end
466
+ end
467
+ end
468
+ end
469
+
470
+ # @!visibility private
471
+ def reset_app_sandbox_internal_sdk_lt_8
472
+ prefs_dir = app_library_preferences_dir
473
+ RunLoop::Directory.recursive_glob_for_entries(prefs_dir).each do |entry|
474
+ if entry.end_with?('.GlobalPreferences.plist') ||
475
+ entry.end_with?('com.apple.PeoplePicker.plist')
476
+ # nop
477
+ else
478
+ if File.exist?(entry)
479
+ FileUtils.rm_rf entry
480
+ end
481
+ end
482
+ end
483
+
484
+ # app preferences lives in device Library/Preferences
485
+ device_prefs_dir = File.join(app_data_dir, 'Library', 'Preferences')
486
+ app_prefs_plist = File.join(device_prefs_dir, "#{app.bundle_identifier}.plist")
487
+ if File.exist?(app_prefs_plist)
488
+ FileUtils.rm_rf(app_prefs_plist)
489
+ end
490
+ end
491
+
492
+ # @!visibility private
493
+ def reset_app_sandbox_internal
494
+ reset_app_sandbox_internal_shared
495
+
496
+ if is_sdk_8?
497
+ reset_app_sandbox_internal_sdk_gte_8
498
+ else
499
+ reset_app_sandbox_internal_sdk_lt_8
500
+ end
501
+ end
502
+
503
+ # @!visibility private
504
+ def app_uia_crash_logs
505
+ base_dir = app_data_dir
506
+ if base_dir.nil?
507
+ nil
508
+ else
509
+ dir = File.join(base_dir, 'Library', 'CrashReporter', 'UIALogs')
510
+ if Dir.exist?(dir)
511
+ Dir.glob("#{dir}/*.plist")
512
+ else
513
+ nil
514
+ end
515
+ end
516
+ end
324
517
  end
325
518
  end
@@ -0,0 +1,20 @@
1
+ module RunLoop
2
+ module Simctl
3
+ class Plists
4
+
5
+ SIMCTL_PLIST_DIR = lambda {
6
+ dirname = File.dirname(__FILE__)
7
+ joined = File.join(dirname, '..', '..', '..', 'plists', 'simctl')
8
+ File.expand_path(joined)
9
+ }.call
10
+
11
+ def self.uia_automation_plist
12
+ File.join(SIMCTL_PLIST_DIR, 'com.apple.UIAutomation.plist')
13
+ end
14
+
15
+ def self.uia_automation_plugin_plist
16
+ File.join(SIMCTL_PLIST_DIR, 'com.apple.UIAutomationPlugIn.plist')
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,5 +1,5 @@
1
1
  module RunLoop
2
- VERSION = '1.3.1'
2
+ VERSION = '1.3.2'
3
3
 
4
4
  # A model of a software release version that can be used to compare two versions.
5
5
  #
@@ -15,6 +15,14 @@ module RunLoop
15
15
  # @todo Refactor instruments related code to instruments class.
16
16
  class XCTools
17
17
 
18
+ # Returns a version instance for `Xcode 6.4`; used to check for the
19
+ # availability of features and paths to various items on the filesystem.
20
+ #
21
+ # @return [RunLoop::Version] 6.3
22
+ def v64
23
+ @xc64 ||= RunLoop::Version.new('6.4')
24
+ end
25
+
18
26
  # Returns a version instance for `Xcode 6.3`; used to check for the
19
27
  # availability of features and paths to various items on the filesystem.
20
28
  #
@@ -63,9 +71,16 @@ module RunLoop
63
71
  @xc50 ||= RunLoop::Version.new('5.0')
64
72
  end
65
73
 
74
+ # Are we running Xcode 6.4 or above?
75
+ #
76
+ # @return [Boolean] `true` if the current Xcode version is >= 6.4
77
+ def xcode_version_gte_64?
78
+ @xcode_gte_64 ||= xcode_version >= v64
79
+ end
80
+
66
81
  # Are we running Xcode 6.3 or above?
67
82
  #
68
- # @return [Boolean] `true` if the current Xcode version is >= 6.2
83
+ # @return [Boolean] `true` if the current Xcode version is >= 6.3
69
84
  def xcode_version_gte_63?
70
85
  @xcode_gte_63 ||= xcode_version >= v63
71
86
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: run_loop
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karl Krukow
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-12 00:00:00.000000000 Z
11
+ date: 2015-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -287,8 +287,10 @@ files:
287
287
  - lib/run_loop/cli/cli.rb
288
288
  - lib/run_loop/cli/errors.rb
289
289
  - lib/run_loop/cli/instruments.rb
290
+ - lib/run_loop/cli/simctl.rb
290
291
  - lib/run_loop/core.rb
291
292
  - lib/run_loop/device.rb
293
+ - lib/run_loop/directory.rb
292
294
  - lib/run_loop/dylib_injector.rb
293
295
  - lib/run_loop/environment.rb
294
296
  - lib/run_loop/fifo.rb
@@ -304,8 +306,11 @@ files:
304
306
  - lib/run_loop/process_waiter.rb
305
307
  - lib/run_loop/sim_control.rb
306
308
  - lib/run_loop/simctl/bridge.rb
309
+ - lib/run_loop/simctl/plists.rb
307
310
  - lib/run_loop/version.rb
308
311
  - lib/run_loop/xctools.rb
312
+ - plists/simctl/com.apple.UIAutomation.plist
313
+ - plists/simctl/com.apple.UIAutomationPlugIn.plist
309
314
  - scripts/calabash_script_uia.js
310
315
  - scripts/json2-min.js
311
316
  - scripts/json2.js