run_loop 1.5.5 → 1.5.6.pre1

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.
@@ -1,73 +0,0 @@
1
- module RunLoop
2
-
3
- # @!visibility private
4
- module LifeCycle
5
-
6
- # @!visibility private
7
- #
8
- # Defines a Life Cycle interface for Simulators.
9
- class Simulator
10
-
11
- # @!visibility private
12
- # Pattern.
13
- # [ '< process name >', < send term first > ]
14
- MANAGED_PROCESSES =
15
- [
16
- # This process is a daemon, and requires 'KILL' to terminate.
17
- # Killing the process is fast, but it takes a long time to
18
- # restart.
19
- # ['com.apple.CoreSimulator.CoreSimulatorService', false],
20
-
21
- # Probably do not need to quit this, but it is tempting to do so.
22
- #['com.apple.CoreSimulator.SimVerificationService', false],
23
-
24
- # Started by Xamarin Studio, this is the parent process of the
25
- # processes launched by Xamarin's interaction with
26
- # CoreSimulatorBridge.
27
- ['csproxy', true],
28
-
29
- # Yes.
30
- ['SimulatorBridge', true],
31
- ['configd_sim', true],
32
- ['launchd_sim', true],
33
-
34
- # Does not always appear.
35
- ['CoreSimulatorBridge', true],
36
-
37
- # assetsd instances clobber each other and are not properly
38
- # killed when quiting the simulator.
39
- ['assetsd', true],
40
-
41
- # iproxy is started by UITest. It is not necessary to send
42
- # TERM first.
43
- ['iproxy', false],
44
-
45
- # Xcode 7
46
- ['ids_simd', true]
47
- ]
48
-
49
- # @!visibility private
50
- def terminate_core_simulator_processes
51
- MANAGED_PROCESSES.each do |pair|
52
- name = pair[0]
53
- send_term = pair[1]
54
- pids = RunLoop::ProcessWaiter.new(name).pids
55
- pids.each do |pid|
56
-
57
- if send_term
58
- term = RunLoop::ProcessTerminator.new(pid, 'TERM', name)
59
- killed = term.kill_process
60
- else
61
- killed = false
62
- end
63
-
64
- unless killed
65
- term = RunLoop::ProcessTerminator.new(pid, 'KILL', name)
66
- term.kill_process
67
- end
68
- end
69
- end
70
- end
71
- end
72
- end
73
- end
@@ -1,499 +0,0 @@
1
- require 'fileutils'
2
- require 'open3'
3
- #require 'retriable'
4
-
5
- module RunLoop::Simctl
6
-
7
- class SimctlError < StandardError
8
-
9
- end
10
-
11
- # @!visibility private
12
- # This is not a public API. You have been warned.
13
- #
14
- # TODO Some code is duplicated from sim_control.rb
15
- # TODO Reinstall if checksum does not match.
16
- # TODO Analyze terminate_core_simulator_processes.
17
- # TODO Figure out when CoreSimulator appears and does not appear.
18
- class Bridge < RunLoop::LifeCycle::CoreSimulator
19
-
20
- attr_reader :device
21
- attr_reader :app
22
- attr_reader :sim_control
23
- attr_reader :pbuddy
24
-
25
- def initialize(device, app_bundle_path)
26
-
27
- @pbuddy = RunLoop::PlistBuddy.new
28
-
29
- @sim_control = RunLoop::SimControl.new
30
- @path_to_ios_sim_app_bundle = @sim_control.send(:sim_app_path)
31
-
32
- @app = RunLoop::App.new(app_bundle_path)
33
-
34
- unless @app.valid?
35
- raise "Could not recreate a valid app from '#{app_bundle_path}'"
36
- end
37
-
38
- @device = device
39
-
40
- # It may seem weird to do this in the initialize, but you cannot make
41
- # simctl calls successfully unless the simulator is:
42
- # 1. closed
43
- # 2. the device you are trying to operate on is Shutdown
44
- # 3. the CoreSimulator processes are terminated
45
- RunLoop::SimControl.terminate_all_sims
46
- shutdown
47
- terminate_core_simulator_processes
48
- end
49
-
50
- # The sha1 of the installed app.
51
- def installed_app_sha1
52
- installed_bundle = fetch_app_dir
53
- if installed_bundle
54
- RunLoop::Directory.directory_digest(installed_bundle)
55
- else
56
- nil
57
- end
58
- end
59
-
60
- # Is the app that is install the same as the one we have in hand?
61
- def same_sha1_as_installed?
62
- app.sha1 == installed_app_sha1
63
- end
64
-
65
- # @!visibility private
66
- def is_sdk_8?
67
- @is_sdk_8 ||= device.version >= RunLoop::Version.new('8.0')
68
- end
69
-
70
- def device_data_dir
71
- @device_data_dir ||= File.join(CORE_SIMULATOR_DEVICE_DIR, device.udid, 'data')
72
- end
73
-
74
- def device_applications_dir
75
- @simulator_app_dir ||= lambda {
76
- if is_sdk_8?
77
- File.join(device_data_dir, 'Containers', 'Bundle', 'Application')
78
- else
79
- File.join(device_data_dir, 'Applications')
80
- end
81
- }.call
82
- end
83
-
84
- def app_data_dir
85
- app_install_dir = fetch_app_dir
86
- return nil if app_install_dir.nil?
87
- if is_sdk_8?
88
- containers_data_dir = File.join(device_data_dir, 'Containers', 'Data', 'Application')
89
- apps = Dir.glob("#{containers_data_dir}/**/#{METADATA_PLIST}")
90
- match = apps.detect do |metadata_plist|
91
- pbuddy.plist_read('MCMMetadataIdentifier', metadata_plist) == app.bundle_identifier
92
- end
93
- if match
94
- File.dirname(match)
95
- else
96
- nil
97
- end
98
- else
99
- app_install_dir
100
- end
101
- end
102
-
103
- def app_library_dir
104
- base_dir = app_data_dir
105
- if base_dir.nil?
106
- nil
107
- else
108
- File.join(base_dir, 'Library')
109
- end
110
- end
111
-
112
- def app_library_preferences_dir
113
- base_dir = app_library_dir
114
- if base_dir.nil?
115
- nil
116
- else
117
- File.join(base_dir, 'Preferences')
118
- end
119
- end
120
-
121
- def app_documents_dir
122
- base_dir = app_data_dir
123
- if base_dir.nil?
124
- nil
125
- else
126
- File.join(base_dir, 'Documents')
127
- end
128
- end
129
-
130
- def app_tmp_dir
131
- base_dir = app_data_dir
132
- if base_dir.nil?
133
- nil
134
- else
135
- File.join(base_dir, 'tmp')
136
- end
137
- end
138
-
139
- def reset_app_sandbox
140
- return true if !app_is_installed?
141
-
142
- shutdown
143
-
144
- reset_app_sandbox_internal
145
- end
146
-
147
- def update_device_state(options={})
148
- merged_options = UPDATE_DEVICE_STATE_OPTS.merge(options)
149
-
150
- interval = merged_options[:interval]
151
- tries = merged_options[:tries]
152
-
153
- debug_logging = RunLoop::Environment.debug?
154
-
155
- on_retry = Proc.new do |_, try, elapsed_time, next_interval|
156
- if debug_logging
157
- # Retriable 2.0
158
- if elapsed_time && next_interval
159
- puts "Updating device state attempt #{try} failed in '#{elapsed_time}'; will retry in '#{next_interval}'"
160
- else
161
- puts "Updating device state attempt #{try} failed; will retry in #{interval}"
162
- end
163
- end
164
- end
165
-
166
- retry_opts = RunLoop::RetryOpts.tries_and_interval(tries, interval,
167
- {:on_retry => on_retry,
168
- :on => [SimctlError]
169
- })
170
- matching_device = nil
171
-
172
- Retriable.retriable(retry_opts) do
173
- matching_device = fetch_matching_device
174
-
175
- unless matching_device
176
- raise "simctl could not find device with '#{device.udid}'"
177
- end
178
-
179
- if matching_device.state == nil || matching_device.state == ''
180
- raise SimctlError, "Could not find the state of the device with #{device.udid}"
181
- end
182
- end
183
-
184
- # Device#state is immutable
185
- @device = matching_device
186
- @device.state
187
- end
188
-
189
- def wait_for_device_state(target_state)
190
- return true if update_device_state == target_state
191
-
192
- now = Time.now
193
- timeout = WAIT_FOR_DEVICE_STATE_OPTS[:timeout]
194
- poll_until = now + WAIT_FOR_DEVICE_STATE_OPTS[:timeout]
195
- delay = WAIT_FOR_DEVICE_STATE_OPTS[:interval]
196
- in_state = false
197
- while Time.now < poll_until
198
- in_state = update_device_state == target_state
199
- break if in_state
200
- sleep delay
201
- end
202
-
203
- if RunLoop::Environment.debug?
204
- puts "Waited for #{timeout} seconds for device to have state: '#{target_state}'."
205
- end
206
-
207
- unless in_state
208
- raise "Expected '#{target_state} but found '#{device.state}' after waiting."
209
- end
210
- in_state
211
- end
212
-
213
- def app_is_installed?
214
- !fetch_app_dir.nil?
215
- end
216
-
217
- # @!visibility private
218
- def fetch_app_dir
219
- sim_app_dir = device_applications_dir
220
- return nil if !File.exist?(sim_app_dir)
221
- Dir.glob("#{sim_app_dir}/**/*.app").detect(nil) do |path|
222
- RunLoop::App.new(path).bundle_identifier == app.bundle_identifier
223
- end
224
- end
225
-
226
- def wait_for_app_install
227
- return true if app_is_installed?
228
-
229
- now = Time.now
230
- timeout = WAIT_FOR_APP_INSTALL_OPTS[:timeout]
231
- poll_until = now + timeout
232
- delay = WAIT_FOR_APP_INSTALL_OPTS[:interval]
233
- is_installed = false
234
- while Time.now < poll_until
235
- is_installed = app_is_installed?
236
- break if is_installed
237
- sleep delay
238
- end
239
-
240
- if RunLoop::Environment.debug?
241
- puts "Waited for #{timeout} seconds for '#{app.bundle_identifier}' to install."
242
- end
243
-
244
- unless is_installed
245
- raise "Expected app to be installed on #{device.instruments_identifier}"
246
- end
247
-
248
- true
249
- end
250
-
251
- def wait_for_app_uninstall
252
- return true unless app_is_installed?
253
-
254
- now = Time.now
255
- timeout = WAIT_FOR_APP_INSTALL_OPTS[:timeout]
256
- poll_until = now + timeout
257
- delay = WAIT_FOR_APP_INSTALL_OPTS[:interval]
258
- not_installed = false
259
- while Time.now < poll_until
260
- not_installed = !app_is_installed?
261
- break if not_installed
262
- sleep delay
263
- end
264
-
265
- if RunLoop::Environment.debug?
266
- puts "Waited for #{timeout} seconds for '#{app.bundle_identifier}' to uninstall."
267
- end
268
-
269
- unless not_installed
270
- raise "Expected app to be installed on #{device.instruments_identifier}"
271
- end
272
-
273
- true
274
- end
275
-
276
- def shutdown
277
- return true if update_device_state == 'Shutdown'
278
-
279
- if device.state != 'Booted'
280
- raise "Cannot handle state '#{device.state}' for #{device.instruments_identifier}"
281
- end
282
-
283
- args = "simctl shutdown #{device.udid}".split(' ')
284
- Open3.popen3('xcrun', *args) do |_, _, stderr, status|
285
- err = stderr.read.strip
286
- exit_status = status.value.exitstatus
287
- if exit_status != 0
288
- raise "Could not shutdown #{device.instruments_identifier}: #{exit_status} => '#{err}'"
289
- end
290
- end
291
- wait_for_device_state('Shutdown')
292
- end
293
-
294
- def boot
295
- return true if update_device_state == 'Booted'
296
-
297
- if device.state != 'Shutdown'
298
- raise "Cannot handle state '#{device.state}' for #{device.instruments_identifier}"
299
- end
300
-
301
- args = "simctl boot #{device.udid}".split(' ')
302
- Open3.popen3('xcrun', *args) do |_, _, stderr, status|
303
- err = stderr.read.strip
304
- exit_status = status.value.exitstatus
305
- if exit_status != 0
306
- raise "Could not boot #{device.instruments_identifier}: #{exit_status} => '#{err}'"
307
- end
308
- end
309
- wait_for_device_state('Booted')
310
- end
311
-
312
- def install
313
- return true if app_is_installed?
314
-
315
- boot
316
-
317
- args = "simctl install #{device.udid} #{app.path}".split(' ')
318
- Open3.popen3('xcrun', *args) do |_, _, stderr, process_status|
319
- err = stderr.read.strip
320
- exit_status = process_status.value.exitstatus
321
- if exit_status != 0
322
- raise "Could not install '#{app.bundle_identifier}': #{exit_status} => '#{err}'."
323
- end
324
- end
325
-
326
- wait_for_app_install
327
- shutdown
328
- end
329
-
330
- def uninstall
331
- return true unless app_is_installed?
332
-
333
- boot
334
-
335
- args = "simctl uninstall #{device.udid} #{app.bundle_identifier}".split(' ')
336
- Open3.popen3('xcrun', *args) do |_, _, stderr, process_status|
337
- err = stderr.read.strip
338
- exit_status = process_status.value.exitstatus
339
- if exit_status != 0
340
- raise "Could not uninstall '#{app.bundle_identifier}': #{exit_status} => '#{err}'."
341
- end
342
- end
343
-
344
- wait_for_app_uninstall
345
- shutdown
346
- end
347
-
348
- def launch_simulator
349
- args = ['open', '-g', '-a', @path_to_ios_sim_app_bundle, '--args', '-CurrentDeviceUDID', device.udid]
350
- pid = spawn('xcrun', *args)
351
- Process.detach(pid)
352
-
353
- # @todo Does not always appear?
354
- # RunLoop::ProcessWaiter.new('CoreSimulatorBridge', WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
355
- sim_name = @sim_control.send(:sim_name)
356
- RunLoop::ProcessWaiter.new(sim_name, WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
357
- RunLoop::ProcessWaiter.new('SimulatorBridge', WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
358
- wait_for_device_state 'Booted'
359
- sleep(SIM_POST_LAUNCH_WAIT)
360
- end
361
-
362
- def launch
363
-
364
- install
365
- launch_simulator
366
-
367
- args = "simctl launch #{device.udid} #{app.bundle_identifier}".split(' ')
368
- Open3.popen3('xcrun', *args) do |_, _, stderr, process_status|
369
- err = stderr.read.strip
370
- exit_status = process_status.value.exitstatus
371
- unless exit_status == 0
372
- raise "Could not simctl launch '#{app.bundle_identifier}' on '#{device.instruments_identifier}': #{exit_status} => '#{err}'"
373
- end
374
- end
375
-
376
- RunLoop::ProcessWaiter.new(app.executable_name, WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
377
- true
378
- end
379
-
380
- private
381
-
382
- WAIT_FOR_DEVICE_STATE_OPTS =
383
- {
384
- interval: 0.1,
385
- timeout: 5
386
- }
387
-
388
- WAIT_FOR_APP_INSTALL_OPTS =
389
- {
390
- interval: 0.1,
391
- timeout: 20
392
- }
393
-
394
- WAIT_FOR_APP_LAUNCH_OPTS =
395
- {
396
- timeout: 10,
397
- raise_on_timeout: true
398
- }
399
-
400
- UPDATE_DEVICE_STATE_OPTS =
401
- {
402
- :tries => 100,
403
- :interval => 0.1
404
- }
405
-
406
- SIM_POST_LAUNCH_WAIT = RunLoop::Environment.sim_post_launch_wait || 1.0
407
-
408
- METADATA_PLIST = '.com.apple.mobile_container_manager.metadata.plist'
409
- CORE_SIMULATOR_DEVICE_DIR = File.expand_path('~/Library/Developer/CoreSimulator/Devices')
410
-
411
- # @!visibility private
412
- def fetch_matching_device
413
- sim_control.simulators.detect do |sim|
414
- sim.udid == device.udid
415
- end
416
- end
417
-
418
- # @!visibility private
419
- def reset_app_sandbox_internal_shared
420
- [app_documents_dir, app_tmp_dir].each do |dir|
421
- FileUtils.rm_rf dir
422
- FileUtils.mkdir dir
423
- end
424
- end
425
-
426
- # @!visibility private
427
- def reset_app_sandbox_internal_sdk_gte_8
428
- lib_dir = app_library_dir
429
- RunLoop::Directory.recursive_glob_for_entries(lib_dir).each do |entry|
430
- if entry.include?('Preferences')
431
- # nop
432
- else
433
- if File.exist?(entry)
434
- FileUtils.rm_rf(entry)
435
- end
436
- end
437
- end
438
-
439
- prefs_dir = app_library_preferences_dir
440
- protected = ['com.apple.UIAutomation.plist',
441
- 'com.apple.UIAutomationPlugIn.plist']
442
- RunLoop::Directory.recursive_glob_for_entries(prefs_dir).each do |entry|
443
- unless protected.include?(File.basename(entry))
444
- if File.exist?(entry)
445
- FileUtils.rm_rf entry
446
- end
447
- end
448
- end
449
- end
450
-
451
- # @!visibility private
452
- def reset_app_sandbox_internal_sdk_lt_8
453
- prefs_dir = app_library_preferences_dir
454
- RunLoop::Directory.recursive_glob_for_entries(prefs_dir).each do |entry|
455
- if entry.end_with?('.GlobalPreferences.plist') ||
456
- entry.end_with?('com.apple.PeoplePicker.plist')
457
- # nop
458
- else
459
- if File.exist?(entry)
460
- FileUtils.rm_rf entry
461
- end
462
- end
463
- end
464
-
465
- # app preferences lives in device Library/Preferences
466
- device_prefs_dir = File.join(app_data_dir, 'Library', 'Preferences')
467
- app_prefs_plist = File.join(device_prefs_dir, "#{app.bundle_identifier}.plist")
468
- if File.exist?(app_prefs_plist)
469
- FileUtils.rm_rf(app_prefs_plist)
470
- end
471
- end
472
-
473
- # @!visibility private
474
- def reset_app_sandbox_internal
475
- reset_app_sandbox_internal_shared
476
-
477
- if is_sdk_8?
478
- reset_app_sandbox_internal_sdk_gte_8
479
- else
480
- reset_app_sandbox_internal_sdk_lt_8
481
- end
482
- end
483
-
484
- # @!visibility private
485
- def app_uia_crash_logs
486
- base_dir = app_data_dir
487
- if base_dir.nil?
488
- nil
489
- else
490
- dir = File.join(base_dir, 'Library', 'CrashReporter', 'UIALogs')
491
- if Dir.exist?(dir)
492
- Dir.glob("#{dir}/*.plist")
493
- else
494
- nil
495
- end
496
- end
497
- end
498
- end
499
- end