run_loop 1.3.0 → 1.3.1

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: 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