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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0a05a75fc51c334a7120e7d0930c4cb673302138
4
- data.tar.gz: 17cd820907c0d174cc81ad7ff02f9a3b172198d9
3
+ metadata.gz: 37ad2fc838d709c0132334cf1a24b88822664e9b
4
+ data.tar.gz: 818e85d9a7480820bdc7737cf99ed1d42b3aef1a
5
5
  SHA512:
6
- metadata.gz: 198b2bd5e060074724dca694de25d4121ca42d7fca592118713798dff2374703c087f74c196b48a7d179babccabbc47e6cf6720452e19cbff85571227012cd90
7
- data.tar.gz: 87e39e777923cb401a38a165654180ca5d9e5809e86f3fa3f17c591fc16246b4b3c501855f8f444074e625ec6b5beae23a3e9223c8e557442f6ce66d1411f8ed
6
+ metadata.gz: 12670d8f9a3490d292280e03adaf8d74343ed5af14568e257d8333a2314cb4602c4f566dd0ea55318daaaf1d26e7f207a3db10c0e8d8486bf701eefbab8ac8aa
7
+ data.tar.gz: 7fff1abf53e185e59ef0b92c8b851b59c0581300ce5d1f1a851b3e0bfeaad2a26df3dbc476141b1deff84ff4a4974e14102f7f44c869e82ba1c2de695cad05be
@@ -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
- $stderr.puts "\n\n****** Accessibility is not enabled on device/simulator, please enable it *** \n\n"
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
@@ -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
@@ -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
@@ -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
- private
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
- @device = device
20
+
23
21
  @sim_control = RunLoop::SimControl.new
24
- @simulator_app_path ||= lambda {
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
- def fullname
41
- @fullname ||= device.instruments_identifier
42
- end
29
+ unless @app.valid?
30
+ raise "Could not recreate a valid app from '#{app_bundle_path}'"
31
+ end
43
32
 
44
- def bundle_identifier
45
- app.bundle_identifier
46
- end
33
+ @device = device
47
34
 
48
- def executable_name
49
- app.executable_name
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
- current = @sim_control.simulators.detect do |sim|
65
- sim.udid == udid
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
- unless current
68
- raise "simctl could not find device with '#{udid}'"
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
- @device = current
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 #{fullname}"
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 #{fullname}"
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 #{fullname}: #{exit_status} => '#{err}'"
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 #{fullname}"
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 #{fullname}: #{exit_status} => '#{err}'"
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
- `xcrun open -a "#{@simulator_app_path}" --args -CurrentDeviceUDID #{udid}`
200
- sleep(5)
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 '#{fullname}': #{exit_status} => '#{err}'"
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
@@ -1,5 +1,5 @@
1
1
  module RunLoop
2
- VERSION = '1.3.0'
2
+ VERSION = '1.3.1'
3
3
 
4
4
  # A model of a software release version that can be used to compare two versions.
5
5
  #
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.0
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-09 00:00:00.000000000 Z
11
+ date: 2015-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json