run_loop 2.2.4 → 2.3.0

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: dc7f4e59c1a6af511543741a5fffc03cf6cf1ee8
4
- data.tar.gz: 7900504361dc7458895971e2ffd633e6d2caa84d
3
+ metadata.gz: 101e24777ce2bcf5ab9b3383d38bcee5b84e2380
4
+ data.tar.gz: cecaa67f6d26fcbacb586feb4cc8124fd83aeac3
5
5
  SHA512:
6
- metadata.gz: 1a4eefe49c3d3bd0a731290a278fde6926b3f9fd50df0d24b7197abecec76cf13fd8fd67c9c894852fd65dd4791dc5184f7794f4292f45c312d0519a504ed2e2
7
- data.tar.gz: 2f3b8b4f47059a1ec5ee867785e5b7f4eb09e3816b0cc3f87eb173ebf6da3665a7472b6fff3093647b7cfb012c4d63f2c9e68d08584b511a7bbd94cb7d6703c9
6
+ metadata.gz: 8f8ddac54d1977cb746bea4d974e4467d99f90fad5f6597fa5aaff86cabab2fba0cba726aee42c32cf70b78f6986b09f200ad622de295e90710fbe920b16e904
7
+ data.tar.gz: 58c40dc759901a4188ae9499892d9baaecceb8ee8a6648e2bad6e7b966bd6c498da6528bff12f7a9eed3354a5ae67cdf35176d92d92f57e7f01ce4719e4e8e02
@@ -26,6 +26,11 @@ module RunLoop
26
26
 
27
27
 
28
28
  def quit
29
+ if RunLoop::Xcode.new.version_gte_8?
30
+ puts "instruments quit with Xcode 8 is not supported"
31
+ exit 1
32
+ end
33
+
29
34
  signal = options[:signal]
30
35
  ENV['DEBUG'] = '1' if options[:debug]
31
36
  instruments = RunLoop::Instruments.new
@@ -94,6 +99,10 @@ module RunLoop
94
99
  :type => :boolean
95
100
 
96
101
  def launch
102
+ if RunLoop::Xcode.new.version_gte_8?
103
+ puts "Launching applications with Xcode 8 is not supported"
104
+ exit 1
105
+ end
97
106
 
98
107
  debug = options[:debug]
99
108
  original_value = ENV['DEBUG']
@@ -807,8 +807,8 @@ $ xcrun instruments -s templates
807
807
  # Validate the architecture.
808
808
  self.expect_simulator_compatible_arch(device, app)
809
809
 
810
- # Quits the simulator.
811
- core_sim = RunLoop::CoreSimulator.new(device, app, :xcode => xcode)
810
+ RunLoop::CoreSimulator.quit_simulator
811
+ core_sim = RunLoop::CoreSimulator.new(device, app, xcode: xcode)
812
812
 
813
813
  # Calabash 0.x can only reset the app sandbox (true/false).
814
814
  # Calabash 2.x has advanced reset options.
@@ -816,13 +816,11 @@ $ xcrun instruments -s templates
816
816
  core_sim.reset_app_sandbox
817
817
  end
818
818
 
819
- # Will quit the simulator if it is running.
820
819
  # @todo fix accessibility_enabled? so we don't have to quit the sim
821
820
  # SimControl#accessibility_enabled? is always false during Core#prepare_simulator
822
821
  # https://github.com/calabash/run_loop/issues/167
823
822
  simctl.ensure_accessibility(device)
824
823
 
825
- # Will quit the simulator if it is running.
826
824
  # @todo fix software_keyboard_enabled? so we don't have to quit the sim
827
825
  # SimControl#software_keyboard_enabled? is always false during Core#prepare_simulator
828
826
  # https://github.com/calabash/run_loop/issues/168
@@ -30,9 +30,6 @@ class RunLoop::CoreSimulator
30
30
  # This should not be overridden
31
31
  WAIT_FOR_SIMULATOR_STATE_INTERVAL = 0.1
32
32
 
33
- # @!visibility private
34
- @@simulator_pid = nil
35
-
36
33
  # @!visibility private
37
34
  attr_reader :app
38
35
 
@@ -70,12 +67,12 @@ class RunLoop::CoreSimulator
70
67
  # Not yet.
71
68
  # "com.apple.CoreSimulator.SimVerificationService",
72
69
 
73
- 'SimulatorBridge',
74
- 'configd_sim',
75
- 'CoreSimulatorBridge',
70
+ "SimulatorBridge",
71
+ "configd_sim",
72
+ "CoreSimulatorBridge",
76
73
 
77
74
  # Xcode 7
78
- 'ids_simd'
75
+ "ids_simd"
79
76
  ]
80
77
 
81
78
  # @!visibility private
@@ -84,39 +81,40 @@ class RunLoop::CoreSimulator
84
81
  SIMULATOR_QUIT_PROCESSES =
85
82
  [
86
83
  # Xcode 7 start throwing this error.
87
- ['splashboardd', false],
88
-
89
- # Xcode < 5.1
90
- ['iPhone Simulator.app', true],
91
-
92
- # 7.0 < Xcode <= 6.0
93
- ['iOS Simulator.app', true],
84
+ ["splashboardd", false],
94
85
 
95
86
  # Xcode >= 7.0
96
- ['Simulator.app', true],
87
+ ["Simulator", true],
97
88
 
98
- # Multiple launchd_sim processes have been causing problems. This
99
- # is a first pass at investigating what it would mean to kill the
100
- # launchd_sim process.
101
- ['launchd_sim', false],
89
+ # Xcode < 7.0
90
+ ["iOS Simulator", true],
91
+
92
+ # Multiple launchd_sim processes have been causing problems.
93
+ # In theory, killing the parent launchd_sim process should kill
94
+ # child processes like assetsd, but in practice this does not
95
+ # always happen.
96
+ ["launchd_sim", false],
102
97
 
103
98
  # Required for DeviceAgent termination; the simulator hangs otherwise.
104
99
  ["xpcproxy", false],
105
100
 
106
- # Causes crash reports on Xcode < 7.0
107
- ["apsd", true],
108
-
109
101
  # assetsd instances clobber each other and are not properly
110
102
  # killed when quiting the simulator.
111
- ['assetsd', false],
103
+ ["assetsd", false],
112
104
 
113
105
  # iproxy is started by UITest.
114
- ['iproxy', false],
106
+ ["iproxy", false],
115
107
 
116
108
  # Started by Xamarin Studio, this is the parent process of the
117
109
  # processes launched by Xamarin's interaction with
118
110
  # CoreSimulatorBridge.
119
- ['csproxy', false],
111
+ ["csproxy", false],
112
+
113
+ # Hundreds of these processes can be present in Xcode 8 and they
114
+ # appear to influence the behavior of DeviceAgent.
115
+ ["MobileSMSSpotlightImporter", false],
116
+ ["UserEventAgent", false],
117
+ ["mobileassetd", false]
120
118
  ]
121
119
 
122
120
  # @!visibility private
@@ -131,6 +129,29 @@ class RunLoop::CoreSimulator
131
129
  send_term_first = false
132
130
  self.term_or_kill(process_name, send_term_first)
133
131
  end
132
+
133
+ ps_name_fn = lambda do |pid|
134
+ args = ["ps", "-o", "comm=", "-p", pid.to_s]
135
+ out = RunLoop::Shell.run_shell_command(args)[:out]
136
+ if out && out.strip != ""
137
+ out.strip
138
+ else
139
+ "UNKNOWN PROCESS: #{pid}"
140
+ end
141
+ end
142
+
143
+ kill_options = { :timeout => 0.5 }
144
+
145
+ RunLoop::ProcessWaiter.pgrep_f("launchd_sim").each do |pid|
146
+ process_name = ps_name_fn.call(pid)
147
+ RunLoop::ProcessTerminator.new(pid, 'KILL', process_name, kill_options).kill_process
148
+ end
149
+
150
+ RunLoop::ProcessWaiter.pgrep_f("iPhoneSimulator").each do |pid|
151
+ process_name = ps_name_fn.call(pid)
152
+ RunLoop::ProcessTerminator.new(pid, 'KILL', process_name, kill_options).kill_process
153
+ end
154
+
134
155
  end
135
156
 
136
157
  # @!visibility private
@@ -141,8 +162,6 @@ class RunLoop::CoreSimulator
141
162
  send_term_first = process_details[1]
142
163
  self.term_or_kill(process_name, send_term_first)
143
164
  end
144
-
145
- self.simulator_pid = nil
146
165
  end
147
166
 
148
167
  # @!visibility private
@@ -288,16 +307,6 @@ class RunLoop::CoreSimulator
288
307
  installed
289
308
  end
290
309
 
291
- # @!visibility private
292
- def self.simulator_pid
293
- @@simulator_pid
294
- end
295
-
296
- # @!visibility private
297
- def self.simulator_pid=(pid)
298
- @@simulator_pid = pid
299
- end
300
-
301
310
  # @param [RunLoop::Device] device The device.
302
311
  # @param [RunLoop::App] app The application.
303
312
  # @param [Hash] options Controls the behavior of this class.
@@ -305,17 +314,9 @@ class RunLoop::CoreSimulator
305
314
  # @option options :xcode An instance of Xcode to use
306
315
  # simulators in the initialize method.
307
316
  def initialize(device, app, options={})
308
- defaults = { :quit_sim_on_init => true }
309
- merged = defaults.merge(options)
310
-
311
317
  @app = app
312
318
  @device = device
313
-
314
- @xcode = merged[:xcode]
315
-
316
- if merged[:quit_sim_on_init]
317
- RunLoop::CoreSimulator.quit_simulator
318
- end
319
+ @xcode = options[:xcode]
319
320
 
320
321
  # stdio.pipe - can cause problems finding the SHA of a simulator
321
322
  rm_instruments_pipe
@@ -341,26 +342,27 @@ class RunLoop::CoreSimulator
341
342
  @simctl ||= RunLoop::Simctl.new
342
343
  end
343
344
 
345
+ # @!visibility private
346
+ def simulator_requires_relaunch?
347
+ [simulator_state_requires_relaunch?,
348
+ running_apps_require_relaunch?].any?
349
+ end
350
+
344
351
  # Launch the simulator indicated by device.
345
352
  def launch_simulator(options={})
346
353
  merged_options = {
347
354
  :wait_for_stable => true
348
355
  }.merge(options)
349
356
 
350
- if running_simulator_pid != nil
351
- # There is a running simulator.
352
-
353
- # Did we launch it?
354
- if running_simulator_pid == RunLoop::CoreSimulator.simulator_pid
355
- # Nothing to do, we already launched the simulator.
356
- return
357
- else
358
- # We did not launch this simulator; quit it.
359
- RunLoop::CoreSimulator.quit_simulator
360
- end
357
+ if !simulator_requires_relaunch?
358
+ RunLoop.log_debug("Simulator is running and does not require a relaunch.")
359
+ return
361
360
  end
362
361
 
363
- args = ['open', '-g', '-a', sim_app_path, '--args', '-CurrentDeviceUDID', device.udid]
362
+ RunLoop::CoreSimulator.quit_simulator
363
+
364
+ args = ['open', '-g', '-a', sim_app_path, '--args',
365
+ '-CurrentDeviceUDID', device.udid, "LAUNCHED_BY_RUN_LOOP"]
364
366
 
365
367
  RunLoop.log_debug("Launching #{device} with:")
366
368
  RunLoop.log_unix_cmd("xcrun #{args.join(' ')}")
@@ -380,9 +382,6 @@ class RunLoop::CoreSimulator
380
382
  elapsed = Time.now - start_time
381
383
  RunLoop.log_debug("Took #{elapsed} seconds to launch the simulator")
382
384
 
383
- # Keep track of the pid so we can know if we have already launched this sim.
384
- RunLoop::CoreSimulator.simulator_pid = running_simulator_pid
385
-
386
385
  true
387
386
  end
388
387
 
@@ -470,15 +469,14 @@ Could not launch #{app.bundle_identifier} on #{device} after trying #{tries} tim
470
469
  def uninstall_app_and_sandbox
471
470
  return true if !app_is_installed?
472
471
 
473
- launch_simulator
474
-
475
- timeout = DEFAULT_OPTIONS[:uninstall_app_timeout]
476
- simctl.uninstall(device, app, timeout)
477
-
478
- device.simulator_wait_for_stable_state
472
+ uninstall_app_with_simctl
479
473
  true
480
474
  end
481
475
 
476
+ =begin
477
+ PRIVATE METHODS
478
+ =end
479
+
482
480
  private
483
481
 
484
482
  # @!visibility private
@@ -501,6 +499,16 @@ Could not launch #{app.bundle_identifier} on #{device} after trying #{tries} tim
501
499
  kill_options = { :timeout => 0.5 }
502
500
 
503
501
  RunLoop::ProcessWaiter.new(process_name).pids.each do |pid|
502
+
503
+ # We could try to determine if terminating the process will be successful
504
+ # by asking for the parent pid and the user id. This adds another call
505
+ # to `ps` and does not save any time. It is easier to simply let the
506
+ # ProcessTerminator fail. The downside is that a failure will appear
507
+ # in the debug log.
508
+ #
509
+ # macOS is looking more like iOS. Process names like 'mobileassetd' are
510
+ # found in both operating systems.
511
+
504
512
  killed = false
505
513
 
506
514
  if send_term_first
@@ -508,8 +516,9 @@ Could not launch #{app.bundle_identifier} on #{device} after trying #{tries} tim
508
516
  killed = term.kill_process
509
517
  end
510
518
 
511
- unless killed
512
- RunLoop::ProcessTerminator.new(pid, 'KILL', process_name, kill_options)
519
+ if !killed
520
+ term = RunLoop::ProcessTerminator.new(pid, 'KILL', process_name, kill_options)
521
+ term.kill_process
513
522
  end
514
523
  end
515
524
  end
@@ -519,13 +528,13 @@ Could not launch #{app.bundle_identifier} on #{device} after trying #{tries} tim
519
528
  # @return [String] A String suitable for searching for a pid, quitting, or
520
529
  # launching the current simulator.
521
530
  def sim_name
522
- @sim_name ||= lambda {
531
+ @sim_name ||= begin
523
532
  if xcode.version_gte_7?
524
533
  "Simulator"
525
534
  else
526
535
  "iOS Simulator"
527
536
  end
528
- }.call
537
+ end
529
538
  end
530
539
 
531
540
  # @!visibility private
@@ -534,74 +543,114 @@ Could not launch #{app.bundle_identifier} on #{device} after trying #{tries} tim
534
543
  # @return [String] The path to the simulator app for the current version of
535
544
  # Xcode.
536
545
  def sim_app_path
537
- @sim_app_path ||= lambda {
538
- dev_dir = xcode.developer_dir
539
- if xcode.version_gte_7?
540
- "#{dev_dir}/Applications/Simulator.app"
541
- else
542
- "#{dev_dir}/Applications/iOS Simulator.app"
543
- end
544
- }.call
546
+ @sim_app_path ||= begin
547
+ "#{xcode.developer_dir}/Applications/#{sim_name}.app"
548
+ end
545
549
  end
546
550
 
547
551
  # @!visibility private
548
- # Returns the current Simulator pid.
549
- #
550
- # @note Will only search for the current Xcode simulator.
551
552
  #
552
- # @return [Integer, nil] The pid as a String or nil if no process is found.
553
- def running_simulator_pid
553
+ # @return [Hash] details about the running simulator.
554
+ def running_simulator_details
554
555
  process_name = "MacOS/#{sim_name}"
555
556
 
556
- args = ["ps", "x", "-o", "pid,command"]
557
+ args = ["ps", "x", "-o", "pid=,command="]
557
558
  hash = run_shell_command(args)
558
559
 
559
560
  exit_status = hash[:exit_status]
560
561
  if exit_status != 0
561
562
  raise RuntimeError,
562
- %Q{Could not find the pid of #{sim_name} with:
563
+ %Q{Could not find the process details of #{sim_name} with:
563
564
 
564
565
  #{args.join(" ")}
565
566
 
566
- Command exited with status #{exit_status}
567
- Message: '#{hash[:out]}'
567
+ Command exited with status: #{exit_status}
568
+
569
+ '#{hash[:out]}'
568
570
  }
569
571
  end
570
572
 
571
573
  if hash[:out].nil? || hash[:out] == ""
572
574
  raise RuntimeError,
573
- %Q{Could not find the pid of #{sim_name} with:
575
+ %Q{Could not find the process details of #{sim_name} with:
574
576
 
575
577
  #{args.join(" ")}
576
578
 
577
- Command had no output
579
+ Command had no output.
578
580
  }
579
581
  end
580
582
 
581
- lines = hash[:out].split("\n")
583
+ lines = hash[:out].split($-0)
582
584
 
583
585
  match = lines.detect do |line|
584
- line[/#{process_name}/, 0]
586
+ line[/#{process_name}/]
585
587
  end
586
588
 
587
- return nil if match.nil?
589
+ return {} if match.nil?
590
+
591
+ hash = {}
592
+
593
+ pid = match.split(" ").first.strip.to_i
594
+ hash[:pid] = pid
595
+
596
+ hash[:launched_by_run_loop] = match[/LAUNCHED_BY_RUN_LOOP/]
597
+
598
+ hash
599
+ end
600
+
601
+ # @!visibility private
602
+ def uninstall_app_with_simctl
603
+ launch_simulator
604
+
605
+ app_size = RunLoop::Directory.size(app.path, :mb)
606
+ sim_size = device.simulator_size_on_disk
607
+ target_size = sim_size - app_size + 5
608
+
609
+ timeout = DEFAULT_OPTIONS[:install_app_timeout]
610
+ simctl.uninstall(device, app, timeout)
611
+
612
+ current_size = device.simulator_size_on_disk
613
+ start = Time.now
614
+ while current_size > target_size && Time.now < start + 5
615
+ sleep(0.5)
616
+ current_size = device.simulator_size_on_disk
617
+ end
588
618
 
589
- match.split(" ").first.to_i
619
+ elapsed = Time.now - start
620
+ if current_size <= target_size
621
+ RunLoop.log_debug("Waited for #{elapsed} seconds for app to uninstall")
622
+ else
623
+ RunLoop.log_debug("Timed out after #{elapsed} seconds for app to uninstall")
624
+ RunLoop.log_debug("Expected sim size #{current_size} < #{target_size}")
625
+ end
590
626
  end
591
627
 
592
628
  # @!visibility private
593
629
  def install_app_with_simctl
594
630
  launch_simulator
595
631
 
632
+ app_size = RunLoop::Directory.size(app.path, :mb)
633
+ sim_size = device.simulator_size_on_disk
634
+ target_size = sim_size + app_size
635
+
596
636
  timeout = DEFAULT_OPTIONS[:install_app_timeout]
597
637
  simctl.install(device, app, timeout)
598
638
 
599
- # On Xcode 7, we must wait. The app might not be installed otherwise. This
600
- # is particularly true for iPads where the app bundle is installed, but
601
- # SpringBoard does not detect the app has been installed.
602
- #
603
- # On Xcode 8, more testing is needed.
604
- device.simulator_wait_for_stable_state
639
+ current_size = device.simulator_size_on_disk
640
+ start = Time.now
641
+ while current_size <= target_size && Time.now < start + 5
642
+ sleep(0.5)
643
+ current_size = device.simulator_size_on_disk
644
+ end
645
+
646
+ elapsed = Time.now - start
647
+ if current_size > target_size
648
+ RunLoop.log_debug("Waited for #{elapsed} seconds for app to install")
649
+ else
650
+ RunLoop.log_debug("Timed out after #{elapsed} seconds for app to install")
651
+ RunLoop.log_debug("Expected sim size #{current_size} >= #{target_size}")
652
+ end
653
+
605
654
  installed_app_bundle_dir
606
655
  end
607
656
 
@@ -657,6 +706,86 @@ Command had no output
657
706
  device.version >= RunLoop::Version.new('8.0')
658
707
  end
659
708
 
709
+ # @!visibility private
710
+ def simulator_state_requires_relaunch?
711
+ running_sim_details = running_simulator_details
712
+
713
+ # Simulator is not running.
714
+ if !running_sim_details[:pid]
715
+ RunLoop.log_debug("Simulator relaunch required: simulator is not running.")
716
+ return true
717
+ end
718
+
719
+ # Simulator is running, but run-loop did not launch it.
720
+ if !running_sim_details[:launched_by_run_loop]
721
+ RunLoop.log_debug("Simulator relaunch required: simulator was not launched by run_loop")
722
+ return true
723
+ end
724
+
725
+ # No device was passed to initializer.
726
+ return true if device.nil?
727
+
728
+ # Simulator is running, run_loop launched it, but it is not Booted.
729
+ device.update_simulator_state
730
+ if device.state == "Booted"
731
+ RunLoop.log_debug("Simulator relaunch not required: simulator has state 'Booted'")
732
+ false
733
+ else
734
+ RunLoop.log_debug("Simulator relaunch required: simulator does not have state 'Booted'")
735
+ true
736
+ end
737
+ end
738
+
739
+ # @!visibility private
740
+ def running_apps_require_relaunch?
741
+ running_apps = device.simulator_running_app_details
742
+
743
+ if running_apps.empty?
744
+ RunLoop.log_debug("Simulator relaunch not required: no running apps")
745
+ return false
746
+ end
747
+
748
+ # DeviceAgent is running, but it was launched by Xcode.
749
+ if running_apps["XCTRunner"]
750
+ if running_apps["XCTRunner"][:args][/CBX_LAUNCHED_BY_XCODE/]
751
+ RunLoop.log_debug("Simulator relaunch required: XCTRunner is controlled by Xcode")
752
+ return true
753
+ end
754
+ end
755
+
756
+ # No app was passed to initializer.
757
+ return true if app.nil?
758
+
759
+ # AUT is running, but it was not launched by DeviceAgent.
760
+ app_name = app.executable_name
761
+ if running_apps[app_name]
762
+ launch_arg = RunLoop::DeviceAgent::Client::AUT_LAUNCHED_BY_RUN_LOOP_ARG
763
+ if !running_apps[app_name][:args][/#{launch_arg}/]
764
+ RunLoop.log_debug("Simulator relaunch required: AUT is running, but not launched by run-loop")
765
+ return true
766
+ end
767
+ end
768
+
769
+ # This is the UITest behavior. UITest does not inspect the simulator for
770
+ # system apps that are running - it only checks for running user apps.
771
+ #
772
+ # I don't think this condition is necessary, so we'll skip it for now, but
773
+ # capture there is a difference between UITest and run-loop.
774
+ #
775
+ # There is some other application running on the simulator.
776
+ # running_apps.delete("XCTRunner")
777
+ # running_apps.delete(app_name)
778
+ #
779
+ # if running_apps.empty?
780
+ # RunLoop.log_debug("Simulator relaunch not required: only XCTRunner and AUT are running")
781
+ # false
782
+ # else
783
+ # RunLoop.log_debug("Simulator relaunch required: other applications are running")
784
+ # true
785
+ # end
786
+ false
787
+ end
788
+
660
789
  # The data directory for the the device.
661
790
  #
662
791
  # ~/Library/Developer/CoreSimulator/Devices/<UDID>/data
@@ -850,20 +979,8 @@ Command had no output
850
979
  RunLoop.log_debug(" App to launch SHA: #{app_sha}")
851
980
  RunLoop.log_debug("Will install #{app}")
852
981
 
853
- FileUtils.rm_rf installed_app_bundle
854
- RunLoop.log_debug('Deleted the existing app')
855
-
856
- directory = File.expand_path(File.join(installed_app_bundle, '..'))
857
- bundle_name = File.basename(app.path)
858
- target = File.join(directory, bundle_name)
859
-
860
- args = ['ditto', app.path, target]
861
- xcrun.run_command_in_context(args, log_cmd: true)
862
-
863
- RunLoop.log_debug("Installed #{app} on CoreSimulator #{device.udid}")
864
-
865
- clear_device_launch_csstore
866
-
982
+ uninstall_app_with_simctl
983
+ install_app_with_simctl
867
984
  true
868
985
  end
869
986
 
@@ -966,7 +1083,7 @@ Command had no output
966
1083
 
967
1084
  # @!visibility private
968
1085
  def self.user_app_installed?(device, bundle_identifier)
969
- core_sim = self.new(device, bundle_identifier, {:quit_sim_on_init => false})
1086
+ core_sim = self.new(device, bundle_identifier)
970
1087
  sim_apps_dir = core_sim.send(:device_applications_dir)
971
1088
  Dir.glob("#{sim_apps_dir}/**/*.app").find do |path|
972
1089
  RunLoop::App.new(path).bundle_identifier == bundle_identifier