run_loop 1.3.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/run_loop/core.rb +114 -32
- data/lib/run_loop/device.rb +28 -0
- data/lib/run_loop/environment.rb +28 -0
- data/lib/run_loop/process_waiter.rb +1 -1
- data/lib/run_loop/sim_control.rb +154 -4
- data/lib/run_loop/simctl/bridge.rb +134 -51
- data/lib/run_loop/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37ad2fc838d709c0132334cf1a24b88822664e9b
|
4
|
+
data.tar.gz: 818e85d9a7480820bdc7737cf99ed1d42b3aef1a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12670d8f9a3490d292280e03adaf8d74343ed5af14568e257d8333a2314cb4602c4f566dd0ea55318daaaf1d26e7f207a3db10c0e8d8486bf701eefbab8ac8aa
|
7
|
+
data.tar.gz: 7fff1abf53e185e59ef0b92c8b851b59c0581300ce5d1f1a851b3e0bfeaad2a26df3dbc476141b1deff84ff4a4974e14102f7f44c869e82ba1c2de695cad05be
|
data/lib/run_loop/core.rb
CHANGED
@@ -48,23 +48,6 @@ module RunLoop
|
|
48
48
|
RunLoop::Logging.log_debug(logger, "\n" + message)
|
49
49
|
end
|
50
50
|
|
51
|
-
# @deprecated since 1.0.0
|
52
|
-
# still used extensively in calabash-ios launcher
|
53
|
-
def self.above_or_eql_version?(target_version, xcode_version)
|
54
|
-
if target_version.is_a?(RunLoop::Version)
|
55
|
-
target = target_version
|
56
|
-
else
|
57
|
-
target = RunLoop::Version.new(target_version)
|
58
|
-
end
|
59
|
-
|
60
|
-
if xcode_version.is_a?(RunLoop::Version)
|
61
|
-
xcode = xcode_version
|
62
|
-
else
|
63
|
-
xcode = RunLoop::Version.new(xcode_version)
|
64
|
-
end
|
65
|
-
target >= xcode
|
66
|
-
end
|
67
|
-
|
68
51
|
def self.script_for_key(key)
|
69
52
|
if SCRIPTS[key].nil?
|
70
53
|
return nil
|
@@ -122,6 +105,68 @@ module RunLoop
|
|
122
105
|
end
|
123
106
|
end
|
124
107
|
|
108
|
+
# Prepares the simulator for running.
|
109
|
+
#
|
110
|
+
# 1. enabling accessibility and software keyboard
|
111
|
+
# 2. installing / uninstalling apps
|
112
|
+
# 3. @todo resetting the app sandbox
|
113
|
+
#
|
114
|
+
# `Bridge#launch_simulator` launches the targeted iOS Simulator. The
|
115
|
+
# simulator itself has several async tasks that must be completed before
|
116
|
+
# we start interacting with it. If your simulator ends up in a bad state,
|
117
|
+
# you can increase the post-launch wait time by setting the
|
118
|
+
# `CAL_SIM_POST_LAUNCH_WAIT` environment variable. The default wait time
|
119
|
+
# is 1.0. This was arrived at through testing.
|
120
|
+
def self.prepare_simulator(launch_options, sim_control)
|
121
|
+
|
122
|
+
# Respect option passed from Calabash
|
123
|
+
if launch_options[:relaunch_simulator]
|
124
|
+
sim_control.quit_sim
|
125
|
+
end
|
126
|
+
|
127
|
+
if !sim_control.xctools.xcode_version_gte_6?
|
128
|
+
# Xcode 5.1.1
|
129
|
+
|
130
|
+
# Will quit the simulator!
|
131
|
+
sim_control.enable_accessibility_on_sims({:verbose => false})
|
132
|
+
else
|
133
|
+
|
134
|
+
# CoreSimulator
|
135
|
+
|
136
|
+
udid = launch_options[:udid]
|
137
|
+
device = sim_control.simulators.detect do |sim|
|
138
|
+
sim.udid == udid || sim.instruments_identifier == udid
|
139
|
+
end
|
140
|
+
|
141
|
+
if device.nil?
|
142
|
+
raise "Could not find simulator with name or UDID that matches: '#{udid}'"
|
143
|
+
end
|
144
|
+
|
145
|
+
# Will quit the simulator if it is running.
|
146
|
+
# @todo fix accessibility_enabled? so we don't have to quit the sim
|
147
|
+
# SimControl#accessibility_enabled? is always false during Core#prepare_simulator
|
148
|
+
# https://github.com/calabash/run_loop/issues/167
|
149
|
+
sim_control.ensure_accessibility(device)
|
150
|
+
|
151
|
+
# Will quit the simulator if it is running.
|
152
|
+
# @todo fix software_keyboard_enabled? so we don't have to quit the sim
|
153
|
+
# SimControl#software_keyboard_enabled? is always false during Core#prepare_simulator
|
154
|
+
# https://github.com/calabash/run_loop/issues/167
|
155
|
+
sim_control.ensure_software_keyboard(device)
|
156
|
+
|
157
|
+
# Xcode 6.3 instruments cannot launch an app that is already installed on
|
158
|
+
# iOS 8.3 Simulators. See: https://github.com/calabash/calabash-ios/issues/744
|
159
|
+
if sim_control.xctools.xcode_version_gte_63?
|
160
|
+
app_bundle_path = launch_options[:bundle_dir_or_bundle_id]
|
161
|
+
bridge = RunLoop::Simctl::Bridge.new(device, app_bundle_path)
|
162
|
+
|
163
|
+
if bridge.app_is_installed? && !sim_control.sim_is_running?
|
164
|
+
bridge.launch_simulator
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
125
170
|
def self.run_with_options(options)
|
126
171
|
before = Time.now
|
127
172
|
|
@@ -202,9 +247,8 @@ module RunLoop
|
|
202
247
|
merged_options = options.merge(discovered_options)
|
203
248
|
|
204
249
|
if self.simulator_target?(merged_options, sim_control)
|
205
|
-
# @todo only enable accessibility on the targeted simulator
|
206
|
-
sim_control.enable_accessibility_on_sims({:verbose => false})
|
207
250
|
self.expect_compatible_simulator_architecture(merged_options, sim_control)
|
251
|
+
self.prepare_simulator(merged_options, sim_control)
|
208
252
|
end
|
209
253
|
|
210
254
|
self.log_run_loop_options(merged_options, xctools)
|
@@ -409,11 +453,6 @@ module RunLoop
|
|
409
453
|
return udid, bundle_dir_or_bundle_id
|
410
454
|
end
|
411
455
|
|
412
|
-
# @deprecated 1.0.0 replaced with Xctools#version
|
413
|
-
def self.xcode_version(xctools=RunLoop::XCTools.new)
|
414
|
-
xctools.xcode_version.to_s
|
415
|
-
end
|
416
|
-
|
417
456
|
def self.create_uia_pipe(repl_path)
|
418
457
|
begin
|
419
458
|
Timeout::timeout(5, RunLoop::TimeoutError) do
|
@@ -480,6 +519,11 @@ module RunLoop
|
|
480
519
|
cmd.gsub(backquote,backquote*4)
|
481
520
|
end
|
482
521
|
|
522
|
+
def self.log_instruments_error(msg)
|
523
|
+
$stderr.puts "\033[31m\n\n*** #{msg} ***\n\n\033[0m"
|
524
|
+
$stderr.flush
|
525
|
+
end
|
526
|
+
|
483
527
|
def self.read_response(run_loop, expected_index, empty_file_timeout=10, search_for_property='index')
|
484
528
|
debug_read = RunLoop::Environment.debug_read?
|
485
529
|
|
@@ -499,14 +543,31 @@ module RunLoop
|
|
499
543
|
|
500
544
|
if /AXError: Could not auto-register for pid status change/.match(output)
|
501
545
|
if /kAXErrorServerNotFound/.match(output)
|
502
|
-
|
503
|
-
$stderr.flush
|
546
|
+
self.log_instruments_error('Accessibility is not enabled on device/simulator, please enable it.')
|
504
547
|
end
|
505
548
|
raise RunLoop::TimeoutError.new('AXError: Could not auto-register for pid status change')
|
506
549
|
end
|
550
|
+
|
507
551
|
if /Automation Instrument ran into an exception/.match(output)
|
508
552
|
raise RunLoop::TimeoutError.new('Exception while running script')
|
509
553
|
end
|
554
|
+
|
555
|
+
if /FBSOpenApplicationErrorDomain error/.match(output)
|
556
|
+
msg = "Instruments failed to launch app: 'FBSOpenApplicationErrorDomain error 8"
|
557
|
+
if RunLoop::Environment.debug?
|
558
|
+
self.log_instruments_error(msg)
|
559
|
+
end
|
560
|
+
raise RunLoop::TimeoutError.new(msg)
|
561
|
+
end
|
562
|
+
|
563
|
+
if /Error: Script threw an uncaught JavaScript error: unknown JavaScript exception/.match(output)
|
564
|
+
msg = "Instruments failed to launch: because of an unknown JavaScript exception"
|
565
|
+
if RunLoop::Environment.debug?
|
566
|
+
self.log_instruments_error(msg)
|
567
|
+
end
|
568
|
+
raise RunLoop::TimeoutError.new(msg)
|
569
|
+
end
|
570
|
+
|
510
571
|
index_if_found = output.index(START_DELIMITER)
|
511
572
|
if debug_read
|
512
573
|
puts output.gsub('*', '')
|
@@ -556,11 +617,6 @@ module RunLoop
|
|
556
617
|
result
|
557
618
|
end
|
558
619
|
|
559
|
-
# @deprecated 1.0.5
|
560
|
-
def self.pids_for_run_loop(run_loop, &block)
|
561
|
-
RunLoop::Instruments.new.instruments_pids(&block)
|
562
|
-
end
|
563
|
-
|
564
620
|
def self.automation_template(xctools, candidate = RunLoop::Environment.trace_template)
|
565
621
|
unless candidate && File.exist?(candidate)
|
566
622
|
candidate = default_tracetemplate xctools
|
@@ -608,6 +664,32 @@ module RunLoop
|
|
608
664
|
def self.instruments_pids
|
609
665
|
RunLoop::Instruments.new.instruments_pids
|
610
666
|
end
|
611
|
-
end
|
612
667
|
|
668
|
+
# @deprecated 1.0.0 replaced with Xctools#version
|
669
|
+
def self.xcode_version(xctools=RunLoop::XCTools.new)
|
670
|
+
xctools.xcode_version.to_s
|
671
|
+
end
|
672
|
+
|
673
|
+
# @deprecated since 1.0.0
|
674
|
+
# still used extensively in calabash-ios launcher
|
675
|
+
def self.above_or_eql_version?(target_version, xcode_version)
|
676
|
+
if target_version.is_a?(RunLoop::Version)
|
677
|
+
target = target_version
|
678
|
+
else
|
679
|
+
target = RunLoop::Version.new(target_version)
|
680
|
+
end
|
681
|
+
|
682
|
+
if xcode_version.is_a?(RunLoop::Version)
|
683
|
+
xcode = xcode_version
|
684
|
+
else
|
685
|
+
xcode = RunLoop::Version.new(xcode_version)
|
686
|
+
end
|
687
|
+
target >= xcode
|
688
|
+
end
|
689
|
+
|
690
|
+
# @deprecated 1.0.5
|
691
|
+
def self.pids_for_run_loop(run_loop, &block)
|
692
|
+
RunLoop::Instruments.new.instruments_pids(&block)
|
693
|
+
end
|
694
|
+
end
|
613
695
|
end
|
data/lib/run_loop/device.rb
CHANGED
@@ -5,6 +5,9 @@ module RunLoop
|
|
5
5
|
attr_reader :version
|
6
6
|
attr_reader :udid
|
7
7
|
attr_reader :state
|
8
|
+
attr_reader :simulator_root_dir
|
9
|
+
attr_reader :simulator_accessibility_plist_path
|
10
|
+
attr_reader :simulator_preferences_plist_path
|
8
11
|
|
9
12
|
# Create a new device.
|
10
13
|
#
|
@@ -86,5 +89,30 @@ module RunLoop
|
|
86
89
|
raise 'Finding the instruction set of a device requires a third-party tool like ideviceinfo'
|
87
90
|
end
|
88
91
|
end
|
92
|
+
|
93
|
+
def simulator_root_dir
|
94
|
+
@simulator_root_dir ||= lambda {
|
95
|
+
return nil if physical_device?
|
96
|
+
File.join(CORE_SIMULATOR_DEVICE_DIR, udid)
|
97
|
+
}.call
|
98
|
+
end
|
99
|
+
|
100
|
+
def simulator_accessibility_plist_path
|
101
|
+
@simulator_accessibility_plist_path ||= lambda {
|
102
|
+
return nil if physical_device?
|
103
|
+
File.join(simulator_root_dir, 'data/Library/Preferences/com.apple.Accessibility.plist')
|
104
|
+
}.call
|
105
|
+
end
|
106
|
+
|
107
|
+
def simulator_preferences_plist_path
|
108
|
+
@simulator_preferences_plist_path ||= lambda {
|
109
|
+
return nil if physical_device?
|
110
|
+
File.join(simulator_root_dir, 'data/Library/Preferences/com.apple.Preferences.plist')
|
111
|
+
}.call
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
CORE_SIMULATOR_DEVICE_DIR = File.expand_path('~/Library/Developer/CoreSimulator/Devices')
|
89
117
|
end
|
90
118
|
end
|
data/lib/run_loop/environment.rb
CHANGED
@@ -73,5 +73,33 @@ module RunLoop
|
|
73
73
|
value
|
74
74
|
end
|
75
75
|
end
|
76
|
+
|
77
|
+
# Returns the value of CAL_SIM_POST_LAUNCH_WAIT
|
78
|
+
#
|
79
|
+
# Controls how long to wait _after_ the simulator is opened.
|
80
|
+
#
|
81
|
+
# The default wait time is 1.0. This was arrived at through testing.
|
82
|
+
#
|
83
|
+
# In CoreSimulator environments, the iOS Simulator starts many async
|
84
|
+
# processes that must be allowed to finish before we start operating on the
|
85
|
+
# simulator. Until we find the right combination of processes to wait for,
|
86
|
+
# this variable will give us the opportunity to control how long we wait.
|
87
|
+
#
|
88
|
+
# Essential for managed envs like Travis + Jenkins and on slower machines.
|
89
|
+
def self.sim_post_launch_wait
|
90
|
+
value = ENV['CAL_SIM_POST_LAUNCH_WAIT']
|
91
|
+
float = nil
|
92
|
+
begin
|
93
|
+
float = value.to_f
|
94
|
+
rescue NoMethodError => _
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
if float.nil? || float == 0.0
|
99
|
+
nil
|
100
|
+
else
|
101
|
+
float
|
102
|
+
end
|
103
|
+
end
|
76
104
|
end
|
77
105
|
end
|
@@ -13,7 +13,7 @@ module RunLoop
|
|
13
13
|
# Collect a list of Integer pids.
|
14
14
|
# @return [Array<Integer>] An array of integer pids for the `process_name`
|
15
15
|
def pids
|
16
|
-
process_info = `ps x -o pid,comm | grep -v grep | grep #{process_name}`
|
16
|
+
process_info = `ps x -o pid,comm | grep -v grep | grep '#{process_name}'`
|
17
17
|
process_array = process_info.split("\n")
|
18
18
|
process_array.map { |process| process.split(' ').first.strip.to_i }
|
19
19
|
end
|
data/lib/run_loop/sim_control.rb
CHANGED
@@ -82,7 +82,7 @@ module RunLoop
|
|
82
82
|
# @todo Consider migrating apple script call to xctools.
|
83
83
|
def launch_sim(opts={})
|
84
84
|
unless sim_is_running?
|
85
|
-
default_opts = {:post_launch_wait => 2.0,
|
85
|
+
default_opts = {:post_launch_wait => RunLoop::Environment.sim_post_launch_wait || 2.0,
|
86
86
|
:hide_after => false}
|
87
87
|
merged_opts = default_opts.merge(opts)
|
88
88
|
`xcrun open -a "#{sim_app_path}"`
|
@@ -107,7 +107,7 @@ module RunLoop
|
|
107
107
|
# tired of your editor losing focus. :)
|
108
108
|
def relaunch_sim(opts={})
|
109
109
|
default_opts = {:post_quit_wait => 1.0,
|
110
|
-
:post_launch_wait => 2.0,
|
110
|
+
:post_launch_wait => RunLoop::Environment.sim_post_launch_wait || 2.0,
|
111
111
|
:hide_after => false}
|
112
112
|
merged_opts = default_opts.merge(opts)
|
113
113
|
quit_sim(merged_opts)
|
@@ -199,7 +199,7 @@ module RunLoop
|
|
199
199
|
# **NOTE:** This option is ignored in Xcode < 6.
|
200
200
|
def reset_sim_content_and_settings(opts={})
|
201
201
|
default_opts = {:post_quit_wait => 1.0,
|
202
|
-
:post_launch_wait => 3.0,
|
202
|
+
:post_launch_wait => RunLoop::Environment.sim_post_launch_wait || 3.0,
|
203
203
|
:hide_after => false,
|
204
204
|
:sim_udid => nil}
|
205
205
|
merged_opts = default_opts.merge(opts)
|
@@ -333,8 +333,149 @@ module RunLoop
|
|
333
333
|
end
|
334
334
|
end
|
335
335
|
|
336
|
-
|
336
|
+
def accessibility_enabled?(device)
|
337
|
+
plist = device.simulator_accessibility_plist_path
|
338
|
+
return false unless File.exist?(plist)
|
339
|
+
|
340
|
+
if device.version >= RunLoop::Version.new('8.0')
|
341
|
+
plist_hash = SDK_80_ACCESSIBILITY_PROPERTIES_HASH
|
342
|
+
else
|
343
|
+
plist_hash = SDK_LT_80_ACCESSIBILITY_PROPERTIES_HASH
|
344
|
+
end
|
345
|
+
|
346
|
+
plist_hash.each do |_, details|
|
347
|
+
key = details[:key]
|
348
|
+
value = details[:value]
|
349
|
+
|
350
|
+
unless pbuddy.plist_read(key, plist) == "#{value}"
|
351
|
+
return false
|
352
|
+
end
|
353
|
+
end
|
354
|
+
true
|
355
|
+
end
|
356
|
+
|
357
|
+
def ensure_accessibility(device)
|
358
|
+
if accessibility_enabled?(device)
|
359
|
+
true
|
360
|
+
else
|
361
|
+
enable_accessibility(device)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def enable_accessibility(device)
|
366
|
+
debug_logging = RunLoop::Environment.debug?
|
367
|
+
|
368
|
+
quit_sim
|
369
|
+
|
370
|
+
plist_path = device.simulator_accessibility_plist_path
|
371
|
+
|
372
|
+
if device.version >= RunLoop::Version.new('8.0')
|
373
|
+
plist_hash = SDK_80_ACCESSIBILITY_PROPERTIES_HASH
|
374
|
+
else
|
375
|
+
plist_hash = SDK_LT_80_ACCESSIBILITY_PROPERTIES_HASH
|
376
|
+
end
|
377
|
+
|
378
|
+
unless File.exist? plist_path
|
379
|
+
preferences_dir = File.join(device.simulator_root_dir, 'data/Library/Preferences')
|
380
|
+
FileUtils.mkdir_p(preferences_dir)
|
381
|
+
plist = CFPropertyList::List.new
|
382
|
+
data = {}
|
383
|
+
plist.value = CFPropertyList.guess(data)
|
384
|
+
plist.save(plist_path, CFPropertyList::List::FORMAT_BINARY)
|
385
|
+
end
|
386
|
+
|
387
|
+
msgs = []
|
388
|
+
|
389
|
+
successes = plist_hash.map do |hash_key, settings|
|
390
|
+
success = pbuddy.plist_set(settings[:key], settings[:type], settings[:value], plist_path)
|
391
|
+
unless success
|
392
|
+
if debug_logging
|
393
|
+
if settings[:type] == 'bool'
|
394
|
+
value = settings[:value] ? 'YES' : 'NO'
|
395
|
+
else
|
396
|
+
value = settings[:value]
|
397
|
+
end
|
398
|
+
msgs << "could not set #{hash_key} => '#{settings[:key]}' to #{value}"
|
399
|
+
end
|
400
|
+
end
|
401
|
+
success
|
402
|
+
end
|
403
|
+
|
404
|
+
if successes.all?
|
405
|
+
true
|
406
|
+
else
|
407
|
+
return false, msgs
|
408
|
+
end
|
409
|
+
end
|
337
410
|
|
411
|
+
def software_keyboard_enabled?(device)
|
412
|
+
unless xcode_version_gte_51?
|
413
|
+
raise RuntimeError, 'Keyboard enabling is only available on Xcode >= 6'
|
414
|
+
end
|
415
|
+
|
416
|
+
plist = device.simulator_preferences_plist_path
|
417
|
+
return false unless File.exist?(plist)
|
418
|
+
|
419
|
+
CORE_SIMULATOR_KEYBOARD_PROPERTIES_HASH.each do |_, details|
|
420
|
+
key = details[:key]
|
421
|
+
value = details[:value]
|
422
|
+
|
423
|
+
unless pbuddy.plist_read(key, plist) == "#{value}"
|
424
|
+
return false
|
425
|
+
end
|
426
|
+
end
|
427
|
+
true
|
428
|
+
end
|
429
|
+
|
430
|
+
def ensure_software_keyboard(device)
|
431
|
+
if software_keyboard_enabled?(device)
|
432
|
+
true
|
433
|
+
else
|
434
|
+
enable_software_keyboard(device)
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
def enable_software_keyboard(device)
|
439
|
+
debug_logging = RunLoop::Environment.debug?
|
440
|
+
|
441
|
+
quit_sim
|
442
|
+
|
443
|
+
plist_path = device.simulator_preferences_plist_path
|
444
|
+
|
445
|
+
unless File.exist? plist_path
|
446
|
+
preferences_dir = File.join(device.simulator_root_dir, 'data/Library/Preferences')
|
447
|
+
FileUtils.mkdir_p(preferences_dir)
|
448
|
+
plist = CFPropertyList::List.new
|
449
|
+
data = {}
|
450
|
+
plist.value = CFPropertyList.guess(data)
|
451
|
+
plist.save(plist_path, CFPropertyList::List::FORMAT_BINARY)
|
452
|
+
end
|
453
|
+
|
454
|
+
msgs = []
|
455
|
+
|
456
|
+
successes = CORE_SIMULATOR_KEYBOARD_PROPERTIES_HASH.map do |hash_key, settings|
|
457
|
+
success = pbuddy.plist_set(settings[:key], settings[:type], settings[:value], plist_path)
|
458
|
+
unless success
|
459
|
+
if debug_logging
|
460
|
+
if settings[:type] == 'bool'
|
461
|
+
value = settings[:value] ? 'YES' : 'NO'
|
462
|
+
else
|
463
|
+
value = settings[:value]
|
464
|
+
end
|
465
|
+
msgs << "could not set #{hash_key} => '#{settings[:key]}' to #{value}"
|
466
|
+
end
|
467
|
+
end
|
468
|
+
success
|
469
|
+
end
|
470
|
+
|
471
|
+
if successes.all?
|
472
|
+
true
|
473
|
+
else
|
474
|
+
return false, msgs
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
private
|
338
479
|
|
339
480
|
# @!visibility private
|
340
481
|
# The list of possible SDKs for 5.0 <= Xcode < 6.0
|
@@ -449,6 +590,15 @@ module RunLoop
|
|
449
590
|
# and parsing the output of `simctl list sessions`.
|
450
591
|
XCODE_6_SIM_UDID_REGEX = /[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}/.freeze
|
451
592
|
|
593
|
+
CORE_SIMULATOR_KEYBOARD_PROPERTIES_HASH =
|
594
|
+
{
|
595
|
+
:automatic_minimization => {
|
596
|
+
:key => 'AutomaticMinimizationEnabled',
|
597
|
+
:value => 0,
|
598
|
+
:type => 'integer'
|
599
|
+
}
|
600
|
+
}
|
601
|
+
|
452
602
|
# @!visibility private
|
453
603
|
# Returns the current Simulator pid.
|
454
604
|
#
|
@@ -1,73 +1,96 @@
|
|
1
1
|
module RunLoop::Simctl
|
2
2
|
|
3
|
+
class SimctlError < StandardError
|
4
|
+
|
5
|
+
end
|
6
|
+
|
3
7
|
# @!visibility private
|
4
8
|
# This is not a public API. You have been warned.
|
5
9
|
#
|
6
|
-
# Proof of concept for using simctl to install and launch an app on a device.
|
7
|
-
#
|
8
|
-
# Do not use this in production code.
|
9
|
-
#
|
10
|
-
# TODO Rename this class.
|
11
10
|
# TODO Some code is duplicated from sim_control.rb
|
12
|
-
# TODO Uninstall
|
13
11
|
# TODO Reinstall if checksum does not match.
|
14
12
|
# TODO Analyze terminate_core_simulator_processes
|
15
13
|
class Bridge
|
16
14
|
|
17
15
|
attr_reader :device
|
18
|
-
attr_reader :app_bundle_path
|
19
16
|
attr_reader :app
|
17
|
+
attr_reader :sim_control
|
20
18
|
|
21
19
|
def initialize(device, app_bundle_path)
|
22
|
-
|
20
|
+
|
23
21
|
@sim_control = RunLoop::SimControl.new
|
24
|
-
@
|
22
|
+
@path_to_ios_sim_app_bundle = lambda {
|
25
23
|
dev_dir = @sim_control.xctools.xcode_developer_dir
|
26
24
|
"#{dev_dir}/Applications/iOS Simulator.app"
|
27
25
|
}.call
|
28
26
|
|
29
|
-
@app_bundle_path = app_bundle_path
|
30
27
|
@app = RunLoop::App.new(app_bundle_path)
|
31
|
-
RunLoop::SimControl.terminate_all_sims
|
32
|
-
shutdown
|
33
|
-
terminate_core_simulator_processes
|
34
|
-
end
|
35
|
-
|
36
|
-
def udid
|
37
|
-
@udid ||= device.udid
|
38
|
-
end
|
39
28
|
|
40
|
-
|
41
|
-
|
42
|
-
|
29
|
+
unless @app.valid?
|
30
|
+
raise "Could not recreate a valid app from '#{app_bundle_path}'"
|
31
|
+
end
|
43
32
|
|
44
|
-
|
45
|
-
app.bundle_identifier
|
46
|
-
end
|
33
|
+
@device = device
|
47
34
|
|
48
|
-
|
49
|
-
|
35
|
+
# It may seem weird to do this in the initialize, but you cannot make
|
36
|
+
# simctl calls successfully unless the simulator is:
|
37
|
+
# 1. closed
|
38
|
+
# 2. the device you are trying to operate on is Shutdown
|
39
|
+
# 3. the CoreSimulator processes are terminated
|
40
|
+
RunLoop::SimControl.terminate_all_sims
|
41
|
+
shutdown
|
42
|
+
terminate_core_simulator_processes
|
50
43
|
end
|
51
44
|
|
52
45
|
def simulator_app_dir
|
53
46
|
@simulator_app_dir ||= lambda {
|
54
47
|
device_dir = File.expand_path('~/Library/Developer/CoreSimulator/Devices')
|
55
48
|
if device.version < RunLoop::Version.new('8.0')
|
56
|
-
File.join(device_dir, udid, 'data', 'Applications')
|
49
|
+
File.join(device_dir, device.udid, 'data', 'Applications')
|
57
50
|
else
|
58
|
-
File.join(device_dir, udid, 'data', 'Containers', 'Bundle', 'Application')
|
51
|
+
File.join(device_dir, device.udid, 'data', 'Containers', 'Bundle', 'Application')
|
59
52
|
end
|
60
53
|
}.call
|
61
54
|
end
|
62
55
|
|
63
|
-
def update_device_state
|
64
|
-
|
65
|
-
|
56
|
+
def update_device_state(options={})
|
57
|
+
merged_options = UPDATE_DEVICE_STATE_OPTS.merge(options)
|
58
|
+
debug_logging = RunLoop::Environment.debug?
|
59
|
+
|
60
|
+
interval = merged_options[:interval]
|
61
|
+
tries = merged_options[:tries]
|
62
|
+
|
63
|
+
on_retry = Proc.new do |_, try, elapsed_time, next_interval|
|
64
|
+
if debug_logging
|
65
|
+
# Retriable 2.0
|
66
|
+
if elapsed_time && next_interval
|
67
|
+
puts "Updating device state attempt #{try} failed in '#{elapsed_time}'; will retry in '#{next_interval}'"
|
68
|
+
else
|
69
|
+
puts "Updating device state attempt #{try} failed; will retry in #{interval}"
|
70
|
+
end
|
71
|
+
end
|
66
72
|
end
|
67
|
-
|
68
|
-
|
73
|
+
|
74
|
+
retry_opts = RunLoop::RetryOpts.tries_and_interval(tries, interval,
|
75
|
+
{:on_retry => on_retry,
|
76
|
+
:on => [SimctlError]
|
77
|
+
})
|
78
|
+
matching_device = nil
|
79
|
+
|
80
|
+
Retriable.retriable(retry_opts) do
|
81
|
+
matching_device = fetch_matching_device
|
82
|
+
|
83
|
+
unless matching_device
|
84
|
+
raise "simctl could not find device with '#{device.udid}'"
|
85
|
+
end
|
86
|
+
|
87
|
+
if matching_device.state == nil || matching_device.state == ''
|
88
|
+
raise SimctlError, "Could not find the state of the device with #{device.udid}"
|
89
|
+
end
|
69
90
|
end
|
70
|
-
|
91
|
+
|
92
|
+
# Device#state is immutable
|
93
|
+
@device = matching_device
|
71
94
|
@device.state
|
72
95
|
end
|
73
96
|
|
@@ -86,7 +109,6 @@ module RunLoop::Simctl
|
|
86
109
|
end
|
87
110
|
end
|
88
111
|
|
89
|
-
|
90
112
|
def wait_for_device_state(target_state)
|
91
113
|
return true if update_device_state == target_state
|
92
114
|
|
@@ -112,7 +134,7 @@ module RunLoop::Simctl
|
|
112
134
|
sim_app_dir = simulator_app_dir
|
113
135
|
return false if !File.exist?(sim_app_dir)
|
114
136
|
app_path = Dir.glob("#{sim_app_dir}/**/*.app").detect do |path|
|
115
|
-
RunLoop::App.new(path).bundle_identifier == bundle_identifier
|
137
|
+
RunLoop::App.new(path).bundle_identifier == app.bundle_identifier
|
116
138
|
end
|
117
139
|
|
118
140
|
!app_path.nil?
|
@@ -132,10 +154,33 @@ module RunLoop::Simctl
|
|
132
154
|
sleep delay
|
133
155
|
end
|
134
156
|
|
135
|
-
puts "Waited for #{timeout} seconds for '#{bundle_identifier}' to install."
|
157
|
+
puts "Waited for #{timeout} seconds for '#{app.bundle_identifier}' to install."
|
136
158
|
|
137
159
|
unless is_installed
|
138
|
-
raise "Expected app to be installed on #{
|
160
|
+
raise "Expected app to be installed on #{device.instruments_identifier}"
|
161
|
+
end
|
162
|
+
|
163
|
+
true
|
164
|
+
end
|
165
|
+
|
166
|
+
def wait_for_app_uninstall
|
167
|
+
return true unless app_is_installed?
|
168
|
+
|
169
|
+
now = Time.now
|
170
|
+
timeout = WAIT_FOR_APP_INSTALL_OPTS[:timeout]
|
171
|
+
poll_until = now + timeout
|
172
|
+
delay = WAIT_FOR_APP_INSTALL_OPTS[:interval]
|
173
|
+
not_installed = false
|
174
|
+
while Time.now < poll_until
|
175
|
+
not_installed = !app_is_installed?
|
176
|
+
break if not_installed
|
177
|
+
sleep delay
|
178
|
+
end
|
179
|
+
|
180
|
+
puts "Waited for #{timeout} seconds for '#{app.bundle_identifier}' to uninstall."
|
181
|
+
|
182
|
+
unless not_installed
|
183
|
+
raise "Expected app to be installed on #{device.instruments_identifier}"
|
139
184
|
end
|
140
185
|
|
141
186
|
true
|
@@ -145,15 +190,15 @@ module RunLoop::Simctl
|
|
145
190
|
return true if update_device_state == 'Shutdown'
|
146
191
|
|
147
192
|
if device.state != 'Booted'
|
148
|
-
raise "Cannot handle state '#{device.state}' for #{
|
193
|
+
raise "Cannot handle state '#{device.state}' for #{device.instruments_identifier}"
|
149
194
|
end
|
150
195
|
|
151
|
-
args = "simctl shutdown #{udid}".split(' ')
|
196
|
+
args = "simctl shutdown #{device.udid}".split(' ')
|
152
197
|
Open3.popen3('xcrun', *args) do |_, _, stderr, status|
|
153
198
|
err = stderr.read.strip
|
154
199
|
exit_status = status.value.exitstatus
|
155
200
|
if exit_status != 0
|
156
|
-
raise "Could not shutdown #{
|
201
|
+
raise "Could not shutdown #{device.instruments_identifier}: #{exit_status} => '#{err}'"
|
157
202
|
end
|
158
203
|
end
|
159
204
|
wait_for_device_state('Shutdown')
|
@@ -163,15 +208,15 @@ module RunLoop::Simctl
|
|
163
208
|
return true if update_device_state == 'Booted'
|
164
209
|
|
165
210
|
if device.state != 'Shutdown'
|
166
|
-
raise "Cannot handle state '#{device.state}' for #{
|
211
|
+
raise "Cannot handle state '#{device.state}' for #{device.instruments_identifier}"
|
167
212
|
end
|
168
213
|
|
169
|
-
args = "simctl boot #{udid}".split(' ')
|
214
|
+
args = "simctl boot #{device.udid}".split(' ')
|
170
215
|
Open3.popen3('xcrun', *args) do |_, _, stderr, status|
|
171
216
|
err = stderr.read.strip
|
172
217
|
exit_status = status.value.exitstatus
|
173
218
|
if exit_status != 0
|
174
|
-
raise "Could not boot #{
|
219
|
+
raise "Could not boot #{device.instruments_identifier}: #{exit_status} => '#{err}'"
|
175
220
|
end
|
176
221
|
end
|
177
222
|
wait_for_device_state('Booted')
|
@@ -182,12 +227,12 @@ module RunLoop::Simctl
|
|
182
227
|
|
183
228
|
boot
|
184
229
|
|
185
|
-
args = "simctl install #{udid} #{app.path}".split(' ')
|
230
|
+
args = "simctl install #{device.udid} #{app.path}".split(' ')
|
186
231
|
Open3.popen3('xcrun', *args) do |_, _, stderr, process_status|
|
187
232
|
err = stderr.read.strip
|
188
233
|
exit_status = process_status.value.exitstatus
|
189
234
|
if exit_status != 0
|
190
|
-
raise "Could not install '#{bundle_identifier}': #{exit_status} => '#{err}'."
|
235
|
+
raise "Could not install '#{app.bundle_identifier}': #{exit_status} => '#{err}'."
|
191
236
|
end
|
192
237
|
end
|
193
238
|
|
@@ -195,9 +240,33 @@ module RunLoop::Simctl
|
|
195
240
|
shutdown
|
196
241
|
end
|
197
242
|
|
243
|
+
def uninstall
|
244
|
+
return true unless app_is_installed?
|
245
|
+
|
246
|
+
boot
|
247
|
+
|
248
|
+
args = "simctl uninstall #{device.udid} #{app.bundle_identifier}".split(' ')
|
249
|
+
Open3.popen3('xcrun', *args) do |_, _, stderr, process_status|
|
250
|
+
err = stderr.read.strip
|
251
|
+
exit_status = process_status.value.exitstatus
|
252
|
+
if exit_status != 0
|
253
|
+
raise "Could not uninstall '#{app.bundle_identifier}': #{exit_status} => '#{err}'."
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
wait_for_app_uninstall
|
258
|
+
shutdown
|
259
|
+
end
|
260
|
+
|
198
261
|
def launch_simulator
|
199
|
-
|
200
|
-
|
262
|
+
args = ['open', '-a', @path_to_ios_sim_app_bundle, '--args', '-CurrentDeviceUDID', device.udid]
|
263
|
+
pid = spawn('xcrun', *args)
|
264
|
+
Process.detach(pid)
|
265
|
+
RunLoop::ProcessWaiter.new('CoreSimulatorBridge', WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
|
266
|
+
RunLoop::ProcessWaiter.new('iOS Simulator', WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
|
267
|
+
RunLoop::ProcessWaiter.new('SimulatorBridge', WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
|
268
|
+
wait_for_device_state 'Booted'
|
269
|
+
sleep(SIM_POST_LAUNCH_WAIT)
|
201
270
|
end
|
202
271
|
|
203
272
|
def launch
|
@@ -205,16 +274,16 @@ module RunLoop::Simctl
|
|
205
274
|
install
|
206
275
|
launch_simulator
|
207
276
|
|
208
|
-
args = "simctl launch #{udid} #{bundle_identifier}".split(' ')
|
277
|
+
args = "simctl launch #{device.udid} #{app.bundle_identifier}".split(' ')
|
209
278
|
Open3.popen3('xcrun', *args) do |_, _, stderr, process_status|
|
210
279
|
err = stderr.read.strip
|
211
280
|
exit_status = process_status.value.exitstatus
|
212
281
|
unless exit_status == 0
|
213
|
-
raise "Could not simctl launch '#{bundle_identifier}' on '#{
|
282
|
+
raise "Could not simctl launch '#{app.bundle_identifier}' on '#{device.instruments_identifier}': #{exit_status} => '#{err}'"
|
214
283
|
end
|
215
284
|
end
|
216
285
|
|
217
|
-
RunLoop::ProcessWaiter.new(executable_name, WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
|
286
|
+
RunLoop::ProcessWaiter.new(app.executable_name, WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
|
218
287
|
true
|
219
288
|
end
|
220
289
|
|
@@ -238,5 +307,19 @@ module RunLoop::Simctl
|
|
238
307
|
raise_on_timeout: true
|
239
308
|
}
|
240
309
|
|
310
|
+
UPDATE_DEVICE_STATE_OPTS =
|
311
|
+
{
|
312
|
+
:tries => 100,
|
313
|
+
:interval => 0.1
|
314
|
+
}
|
315
|
+
|
316
|
+
SIM_POST_LAUNCH_WAIT = RunLoop::Environment.sim_post_launch_wait || 1.0
|
317
|
+
|
318
|
+
# @!visibility private
|
319
|
+
def fetch_matching_device
|
320
|
+
sim_control.simulators.detect do |sim|
|
321
|
+
sim.udid == device.udid
|
322
|
+
end
|
323
|
+
end
|
241
324
|
end
|
242
325
|
end
|
data/lib/run_loop/version.rb
CHANGED
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.
|
4
|
+
version: 1.3.1
|
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-
|
11
|
+
date: 2015-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|