run_loop_tcc 2.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/bin/run-loop +19 -0
  4. data/lib/run_loop/abstract.rb +18 -0
  5. data/lib/run_loop/app.rb +372 -0
  6. data/lib/run_loop/cache/cache.rb +68 -0
  7. data/lib/run_loop/cli/cli.rb +48 -0
  8. data/lib/run_loop/cli/codesign.rb +24 -0
  9. data/lib/run_loop/cli/errors.rb +11 -0
  10. data/lib/run_loop/cli/instruments.rb +160 -0
  11. data/lib/run_loop/cli/locale.rb +31 -0
  12. data/lib/run_loop/cli/simctl.rb +257 -0
  13. data/lib/run_loop/cli/tcc.rb +139 -0
  14. data/lib/run_loop/codesign.rb +76 -0
  15. data/lib/run_loop/core.rb +902 -0
  16. data/lib/run_loop/core_simulator.rb +960 -0
  17. data/lib/run_loop/detect_aut/detect.rb +185 -0
  18. data/lib/run_loop/detect_aut/errors.rb +126 -0
  19. data/lib/run_loop/detect_aut/xamarin_studio.rb +46 -0
  20. data/lib/run_loop/detect_aut/xcode.rb +157 -0
  21. data/lib/run_loop/device.rb +722 -0
  22. data/lib/run_loop/device_agent/app/CBX-Runner.app.zip +0 -0
  23. data/lib/run_loop/device_agent/bin/xctestctl +0 -0
  24. data/lib/run_loop/device_agent/cbxrunner.rb +156 -0
  25. data/lib/run_loop/device_agent/frameworks/Frameworks.zip +0 -0
  26. data/lib/run_loop/device_agent/frameworks.rb +65 -0
  27. data/lib/run_loop/device_agent/ipa/CBX-Runner.app.zip +0 -0
  28. data/lib/run_loop/device_agent/launcher.rb +51 -0
  29. data/lib/run_loop/device_agent/xcodebuild.rb +91 -0
  30. data/lib/run_loop/device_agent/xctestctl.rb +109 -0
  31. data/lib/run_loop/directory.rb +179 -0
  32. data/lib/run_loop/dnssd.rb +148 -0
  33. data/lib/run_loop/dot_dir.rb +87 -0
  34. data/lib/run_loop/dylib_injector.rb +145 -0
  35. data/lib/run_loop/encoding.rb +56 -0
  36. data/lib/run_loop/environment.rb +361 -0
  37. data/lib/run_loop/fifo.rb +40 -0
  38. data/lib/run_loop/host_cache.rb +128 -0
  39. data/lib/run_loop/http/error.rb +15 -0
  40. data/lib/run_loop/http/request.rb +44 -0
  41. data/lib/run_loop/http/retriable_client.rb +166 -0
  42. data/lib/run_loop/http/server.rb +17 -0
  43. data/lib/run_loop/instruments.rb +436 -0
  44. data/lib/run_loop/ipa.rb +142 -0
  45. data/lib/run_loop/l10n.rb +93 -0
  46. data/lib/run_loop/language.rb +63 -0
  47. data/lib/run_loop/lipo.rb +132 -0
  48. data/lib/run_loop/lldb.rb +52 -0
  49. data/lib/run_loop/locale.rb +101 -0
  50. data/lib/run_loop/logging.rb +111 -0
  51. data/lib/run_loop/otool.rb +76 -0
  52. data/lib/run_loop/patches/awesome_print.rb +17 -0
  53. data/lib/run_loop/physical_device/life_cycle.rb +268 -0
  54. data/lib/run_loop/plist_buddy.rb +189 -0
  55. data/lib/run_loop/process_terminator.rb +128 -0
  56. data/lib/run_loop/process_waiter.rb +117 -0
  57. data/lib/run_loop/regex.rb +19 -0
  58. data/lib/run_loop/shell.rb +103 -0
  59. data/lib/run_loop/sim_control.rb +1264 -0
  60. data/lib/run_loop/simctl.rb +275 -0
  61. data/lib/run_loop/sqlite.rb +61 -0
  62. data/lib/run_loop/strings.rb +88 -0
  63. data/lib/run_loop/tcc/TCC.db +0 -0
  64. data/lib/run_loop/tcc/tcc.rb +240 -0
  65. data/lib/run_loop/template.rb +61 -0
  66. data/lib/run_loop/version.rb +182 -0
  67. data/lib/run_loop/xcode.rb +318 -0
  68. data/lib/run_loop/xcrun.rb +107 -0
  69. data/lib/run_loop/xcuitest.rb +550 -0
  70. data/lib/run_loop.rb +230 -0
  71. data/plists/simctl/com.apple.UIAutomation.plist +0 -0
  72. data/plists/simctl/com.apple.UIAutomationPlugIn.plist +0 -0
  73. data/scripts/calabash_script_uia.js +28184 -0
  74. data/scripts/lib/json2.min.js +26 -0
  75. data/scripts/lib/log.js +26 -0
  76. data/scripts/lib/on_alert.js +224 -0
  77. data/scripts/read-cmd.sh +2 -0
  78. data/scripts/run_dismiss_location.js +89 -0
  79. data/scripts/run_loop_basic.js +34 -0
  80. data/scripts/run_loop_fast_uia.js +188 -0
  81. data/scripts/run_loop_host.js +117 -0
  82. data/scripts/run_loop_shared_element.js +125 -0
  83. data/scripts/timeout3 +23 -0
  84. data/scripts/udidetect +0 -0
  85. data/vendor-licenses/FBSimulatorControl.LICENSE +30 -0
  86. data/vendor-licenses/xctestctl.LICENSE +32 -0
  87. metadata +443 -0
@@ -0,0 +1,960 @@
1
+ # A class to manage interactions with CoreSimulators.
2
+ class RunLoop::CoreSimulator
3
+
4
+ # These options control various aspects of an app's life cycle on the iOS
5
+ # Simulator.
6
+ #
7
+ # You can override these values if they do not work in your environment.
8
+ #
9
+ # For cucumber users, the best place to override would be in your
10
+ # features/support/env.rb.
11
+ #
12
+ # For example:
13
+ #
14
+ # RunLoop::CoreSimulator::DEFAULT_OPTIONS[:install_app_timeout] = 60
15
+ DEFAULT_OPTIONS = {
16
+ # In most cases 30 seconds is a reasonable amount of time to wait for an
17
+ # install. When testing larger apps, on slow machines, or in CI, this
18
+ # value may need to be higher. 120 is the default for CI.
19
+ :install_app_timeout => RunLoop::Environment.ci? ? 120 : 30,
20
+ :uninstall_app_timeout => RunLoop::Environment.ci? ? 120 : 30,
21
+ :launch_app_timeout => RunLoop::Environment.ci? ? 120 : 30,
22
+ :wait_for_state_timeout => RunLoop::Environment.ci? ? 120 : 30
23
+ }
24
+
25
+ # @!visibility private
26
+ # This should not be overridden
27
+ WAIT_FOR_SIMULATOR_STATE_INTERVAL = 0.1
28
+
29
+ # @!visibility private
30
+ @@simulator_pid = nil
31
+
32
+ # @!visibility private
33
+ attr_reader :app
34
+
35
+ # @!visibility private
36
+ attr_reader :device
37
+
38
+ # @!visibility private
39
+ attr_reader :pbuddy
40
+
41
+ # @!visibility private
42
+ attr_reader :xcode
43
+
44
+ # @!visibility private
45
+ attr_reader :xcrun
46
+
47
+ # @!visibility private
48
+ METADATA_PLIST = '.com.apple.mobile_container_manager.metadata.plist'
49
+
50
+ # @!visibility private
51
+ CORE_SIMULATOR_DEVICE_DIR = File.join(RunLoop::Environment.user_home_directory,
52
+ "Library",
53
+ "Developer",
54
+ "CoreSimulator",
55
+ "Devices")
56
+
57
+
58
+ # @!visibility private
59
+ MANAGED_PROCESSES =
60
+ [
61
+ # This process is a daemon, and requires 'KILL' to terminate.
62
+ # Killing the process is fast, but it takes a long time to
63
+ # restart.
64
+ ['com.apple.CoreSimulator.CoreSimulatorService', false],
65
+
66
+ # Probably do not need to quit this, but it is tempting to do so.
67
+ #['com.apple.CoreSimulator.SimVerificationService', false],
68
+
69
+ 'SimulatorBridge',
70
+ 'configd_sim',
71
+
72
+ # Does not always appear.
73
+ 'CoreSimulatorBridge',
74
+
75
+ # Xcode 7
76
+ 'ids_simd'
77
+ ]
78
+
79
+ # @!visibility private
80
+ # Pattern:
81
+ # [ '< process name >', < send term first > ]
82
+ SIMULATOR_QUIT_PROCESSES =
83
+ [
84
+ # Xcode 7 start throwing this error.
85
+ ['splashboardd', false],
86
+
87
+ # Xcode < 5.1
88
+ ['iPhone Simulator.app', true],
89
+
90
+ # 7.0 < Xcode <= 6.0
91
+ ['iOS Simulator.app', true],
92
+
93
+ # Xcode >= 7.0
94
+ ['Simulator.app', true],
95
+
96
+ # Multiple launchd_sim processes have been causing problems. This
97
+ # is a first pass at investigating what it would mean to kill the
98
+ # launchd_sim process.
99
+ ['launchd_sim', false],
100
+
101
+ # Required for XCUITest termination; the simulator hangs otherwise.
102
+ ["xpcproxy", false],
103
+
104
+ # Causes crash reports on Xcode < 7.0
105
+ ["apsd", true],
106
+
107
+ # assetsd instances clobber each other and are not properly
108
+ # killed when quiting the simulator.
109
+ ['assetsd', false],
110
+
111
+ # iproxy is started by UITest.
112
+ ['iproxy', false],
113
+
114
+ # Started by Xamarin Studio, this is the parent process of the
115
+ # processes launched by Xamarin's interaction with
116
+ # CoreSimulatorBridge.
117
+ ['csproxy', false],
118
+ ]
119
+
120
+ # @!visibility private
121
+ #
122
+ # Terminate CoreSimulator related processes. This processes can accumulate
123
+ # as testing proceeds and can cause instability.
124
+ def self.terminate_core_simulator_processes
125
+
126
+ self.quit_simulator
127
+
128
+ MANAGED_PROCESSES.each do |process_name|
129
+ send_term_first = false
130
+ self.term_or_kill(process_name, send_term_first)
131
+ end
132
+ end
133
+
134
+ # @!visibility private
135
+ # Quit any Simulator.app or iOS Simulator.app
136
+ def self.quit_simulator
137
+ SIMULATOR_QUIT_PROCESSES.each do |process_details|
138
+ process_name = process_details[0]
139
+ send_term_first = process_details[1]
140
+ self.term_or_kill(process_name, send_term_first)
141
+ end
142
+
143
+ self.simulator_pid = nil
144
+ end
145
+
146
+ # @!visibility private
147
+ #
148
+ # Some operations, like erase, require that the simulator be
149
+ # 'Shutdown'.
150
+ #
151
+ # @param [RunLoop::Device] simulator the sim to wait for
152
+ # @param [String] target_state the state to wait for
153
+ def self.wait_for_simulator_state(simulator, target_state)
154
+ now = Time.now
155
+ timeout = DEFAULT_OPTIONS[:wait_for_state_timeout]
156
+ poll_until = now + timeout
157
+ delay = WAIT_FOR_SIMULATOR_STATE_INTERVAL
158
+ in_state = false
159
+ while Time.now < poll_until
160
+ in_state = simulator.update_simulator_state == target_state
161
+ break if in_state
162
+ sleep delay if delay != 0
163
+ end
164
+
165
+ elapsed = Time.now - now
166
+ RunLoop.log_debug("Waited for #{elapsed} seconds for device to have state: '#{target_state}'.")
167
+
168
+ unless in_state
169
+ raise "Expected '#{target_state} but found '#{simulator.state}' after waiting."
170
+ end
171
+ in_state
172
+ end
173
+
174
+ # @!visibility private
175
+ # Erase a simulator. This is the same as touching the Simulator
176
+ # "Reset Content & Settings" menu item.
177
+ #
178
+ # @param [RunLoop::Device] simulator The simulator to erase
179
+ # @param [Hash] options Control the behavior of the method.
180
+ # @option options [Numeric] :timeout (180) How long tow wait for simctl to
181
+ # shutdown and erase the simulator. The timeout is apply separately to
182
+ # each command.
183
+ #
184
+ # @raise RuntimeError If the simulator cannot be shutdown
185
+ # @raise RuntimeError If the simulator cannot be erased
186
+ # @raise ArgumentError If the simulator is a physical device
187
+ def self.erase(simulator, options={})
188
+ if simulator.physical_device?
189
+ raise ArgumentError,
190
+ "#{simulator} is a physical device. This method is only for Simulators"
191
+ end
192
+
193
+ default_options = {
194
+ :timeout => 60*3
195
+ }
196
+
197
+ merged_options = default_options.merge(options)
198
+
199
+ self.quit_simulator
200
+
201
+ xcrun = merged_options[:xcrun] || RunLoop::Xcrun.new
202
+ timeout = merged_options[:timeout]
203
+ xcrun_opts = {
204
+ :log_cmd => true,
205
+ :timeout => timeout
206
+ }
207
+
208
+ if simulator.update_simulator_state != "Shutdown"
209
+ args = ["simctl", "shutdown", simulator.udid]
210
+ xcrun.run_command_in_context(args, xcrun_opts)
211
+ begin
212
+ self.wait_for_simulator_state(simulator, "Shutdown")
213
+ rescue RuntimeError => _
214
+ raise RuntimeError, %Q{
215
+ Could not erase simulator because it could not be Shutdown.
216
+
217
+ This usually means your CoreSimulator processes need to be restarted.
218
+
219
+ You can restart the CoreSimulator processes with this command:
220
+
221
+ $ bundle exec run-loop simctl manage-processes
222
+
223
+ }
224
+
225
+ end
226
+ end
227
+
228
+ args = ["simctl", "erase", simulator.udid]
229
+ hash = xcrun.run_command_in_context(args, xcrun_opts)
230
+
231
+ if hash[:exit_status] != 0
232
+ raise RuntimeError, %Q{
233
+ Could not erase simulator because simctl returned this error:
234
+
235
+ #{hash[:out]}
236
+
237
+ This usually means your CoreSimulator processes need to be restarted.
238
+
239
+ You can restart the CoreSimulator processes with this command:
240
+
241
+ $ bundle exec run-loop simctl manage-processes
242
+
243
+ }
244
+
245
+ end
246
+
247
+ hash
248
+ end
249
+
250
+ # @!visibility private
251
+ #
252
+ # @param [RunLoop::Device, String] device a simulator UDID, instruments-ready
253
+ # name, or a RunLoop::Device.
254
+ #
255
+ # @param [String] locale_code a locale code
256
+ #
257
+ # @raise [ArgumentError] if no device can be found matching the UDID or
258
+ # instruments-ready name
259
+ # @raise [ArgumentError] if device is not a simulator
260
+ # @raise [ArgumentError] if locale_code is invalid
261
+ def self.set_locale(device, locale_code)
262
+ if device.is_a?(RunLoop::Device)
263
+ simulator = device
264
+ else
265
+ simulator = RunLoop::Device.device_with_identifier(device)
266
+ end
267
+
268
+ if simulator.physical_device?
269
+ raise ArgumentError,
270
+ "The locale cannot be set on physical devices"
271
+ end
272
+
273
+ self.quit_simulator
274
+ RunLoop.log_debug("Setting locale to '#{locale_code}'")
275
+ simulator.simulator_set_locale(locale_code)
276
+ end
277
+
278
+ # @!visibility private
279
+ #
280
+ # @param [RunLoop::Device, String] device a simulator UDID, instruments-ready
281
+ # name, or a RunLoop::Device
282
+ # @param [String] lang_code a language code
283
+ #
284
+ # @raise [ArgumentError] if no device can be found matching the UDID or
285
+ # instruments-ready name
286
+ # @raise [ArgumentError] if device is not a simulator
287
+ # @raise [ArgumentError] if language_code is invalid
288
+ def self.set_language(device, lang_code)
289
+ if device.is_a?(RunLoop::Device)
290
+ simulator = device
291
+ else
292
+ simulator = RunLoop::Device.device_with_identifier(device)
293
+ end
294
+
295
+ if simulator.physical_device?
296
+ raise ArgumentError,
297
+ "The language cannot be set on physical devices"
298
+ end
299
+
300
+ self.quit_simulator
301
+ RunLoop.log_debug("Setting preferred language to '#{lang_code}'")
302
+ simulator.simulator_set_language(lang_code)
303
+ end
304
+
305
+ # @!visibility private
306
+ def self.simulator_pid
307
+ @@simulator_pid
308
+ end
309
+
310
+ # @!visibility private
311
+ def self.simulator_pid=(pid)
312
+ @@simulator_pid = pid
313
+ end
314
+
315
+ # @param [RunLoop::Device] device The device.
316
+ # @param [RunLoop::App] app The application.
317
+ # @param [Hash] options Controls the behavior of this class.
318
+ # @option options :quit_sim_on_init (true) If true, quit any running
319
+ # @option options :xcode An instance of Xcode to use
320
+ # simulators in the initialize method.
321
+ def initialize(device, app, options={})
322
+ defaults = { :quit_sim_on_init => true }
323
+ merged = defaults.merge(options)
324
+
325
+ @app = app
326
+ @device = device
327
+
328
+ @xcode = merged[:xcode]
329
+
330
+ if merged[:quit_sim_on_init]
331
+ RunLoop::CoreSimulator.quit_simulator
332
+ end
333
+
334
+ # stdio.pipe - can cause problems finding the SHA of a simulator
335
+ rm_instruments_pipe
336
+ end
337
+
338
+ # @!visibility private
339
+ def pbuddy
340
+ @pbuddy ||= RunLoop::PlistBuddy.new
341
+ end
342
+
343
+ # @!visibility private
344
+ def xcode
345
+ @xcode ||= RunLoop::Xcode.new
346
+ end
347
+
348
+ # @!visibility private
349
+ def xcrun
350
+ @xcrun ||= RunLoop::Xcrun.new
351
+ end
352
+
353
+ # Launch the simulator indicated by device.
354
+ def launch_simulator
355
+
356
+ if running_simulator_pid != nil
357
+ # There is a running simulator.
358
+
359
+ # Did we launch it?
360
+ if running_simulator_pid == RunLoop::CoreSimulator.simulator_pid
361
+ # Nothing to do, we already launched the simulator.
362
+ return
363
+ else
364
+ # We did not launch this simulator; quit it.
365
+ RunLoop::CoreSimulator.quit_simulator
366
+ end
367
+ end
368
+
369
+ args = ['open', '-g', '-a', sim_app_path, '--args', '-CurrentDeviceUDID', device.udid]
370
+
371
+ RunLoop.log_debug("Launching #{device} with:")
372
+ RunLoop.log_unix_cmd("xcrun #{args.join(' ')}")
373
+
374
+ start_time = Time.now
375
+
376
+ pid = Process.spawn('xcrun', *args)
377
+ Process.detach(pid)
378
+
379
+ options = { :timeout => 5, :raise_on_timeout => true }
380
+ RunLoop::ProcessWaiter.new(sim_name, options).wait_for_any
381
+
382
+ device.simulator_wait_for_stable_state
383
+
384
+ elapsed = Time.now - start_time
385
+ RunLoop.log_debug("Took #{elapsed} seconds to launch the simulator")
386
+
387
+ # Keep track of the pid so we can know if we have already launched this sim.
388
+ RunLoop::CoreSimulator.simulator_pid = running_simulator_pid
389
+
390
+ true
391
+ end
392
+
393
+ # Launch the app on the simulator.
394
+ #
395
+ # 1. If the app is not installed, it is installed.
396
+ # 2. If the app is different from the app that is installed, it is installed.
397
+ def launch
398
+ install
399
+
400
+ # If the app is the same, install will not launch the simulator.
401
+ # In order to launch the app, the simulator needs to be running.
402
+ # launch_simulator ensures that the sim is launched and will not
403
+ # relaunch it.
404
+ launch_simulator
405
+
406
+ tries = RunLoop::Environment.ci? ? 5 : 3
407
+ last_error = nil
408
+
409
+ RunLoop.log_debug("Trying #{tries} times to launch #{app.bundle_identifier} on #{device}")
410
+
411
+ tries.times do |try|
412
+ # Terminates CoreSimulatorService on failures.
413
+ hash = attempt_to_launch_app_with_simctl
414
+
415
+ exit_status = hash[:exit_status]
416
+ if exit_status != 0
417
+ # Last argument is how long to sleep after an error.
418
+ last_error = handle_failed_app_launch(hash, try, tries, 0.5)
419
+ else
420
+ last_error = nil
421
+ break
422
+ end
423
+ end
424
+
425
+ if last_error
426
+ raise RuntimeError, %Q[Could not launch #{app.bundle_identifier} on #{device}
427
+
428
+ #{last_error}
429
+
430
+ ]
431
+ end
432
+
433
+ wait_for_app_launch
434
+ end
435
+
436
+ # @!visibility private
437
+ def wait_for_app_launch
438
+ options = {
439
+ :timeout => 10,
440
+ :raise_on_timeout => true
441
+ }
442
+ RunLoop::ProcessWaiter.new(app.executable_name, options).wait_for_any
443
+ device.simulator_wait_for_stable_state
444
+ true
445
+ end
446
+
447
+ # Install the app.
448
+ #
449
+ # 1. If the app is not installed, it is installed.
450
+ # 2. Does nothing, if the app is the same as the one that is installed.
451
+ # 3. Installs the app if it is different from the installed app.
452
+ #
453
+ # The app sandbox is not touched.
454
+ def install
455
+ installed_app_bundle = installed_app_bundle_dir
456
+
457
+ # App is not installed. Use simctl interface to install.
458
+ if !installed_app_bundle
459
+ installed_app_bundle = install_app_with_simctl
460
+ else
461
+ ensure_app_same
462
+ end
463
+
464
+ installed_app_bundle
465
+ end
466
+
467
+ # Is this app installed?
468
+ def app_is_installed?
469
+ !installed_app_bundle_dir.nil?
470
+ end
471
+
472
+ # Resets the app sandbox.
473
+ #
474
+ # Does nothing if the app is not installed.
475
+ def reset_app_sandbox
476
+ return true if !app_is_installed?
477
+
478
+ RunLoop::CoreSimulator.wait_for_simulator_state(device, "Shutdown")
479
+
480
+ reset_app_sandbox_internal
481
+ end
482
+
483
+ # Uninstalls the app and clears the sandbox.
484
+ def uninstall_app_and_sandbox
485
+ return true if !app_is_installed?
486
+
487
+ launch_simulator
488
+
489
+ args = ['simctl', 'uninstall', device.udid, app.bundle_identifier]
490
+
491
+ timeout = DEFAULT_OPTIONS[:uninstall_app_timeout]
492
+ xcrun.run_command_in_context(args, log_cmd: true, timeout: timeout)
493
+
494
+ device.simulator_wait_for_stable_state
495
+ true
496
+ end
497
+
498
+ private
499
+
500
+ # @!visibility private
501
+ #
502
+ # This stdio.pipe file causes problems when checking the size and taking the
503
+ # checksum of the core simulator directory.
504
+ def rm_instruments_pipe
505
+ device_tmp_dir = File.join(device_data_dir, 'tmp')
506
+ Dir.glob("#{device_tmp_dir}/instruments_*/stdio.pipe") do |file|
507
+ if File.exist?(file)
508
+ RunLoop.log_debug("Deleting #{file}")
509
+ FileUtils.rm_rf(file)
510
+ end
511
+ end
512
+ end
513
+
514
+ # Send 'TERM' then 'KILL' to allow processes to quit cleanly.
515
+ def self.term_or_kill(process_name, send_term_first)
516
+ term_options = { :timeout => 0.5 }
517
+ kill_options = { :timeout => 0.5 }
518
+
519
+ RunLoop::ProcessWaiter.new(process_name).pids.each do |pid|
520
+ killed = false
521
+
522
+ if send_term_first
523
+ term = RunLoop::ProcessTerminator.new(pid, 'TERM', process_name, term_options)
524
+ killed = term.kill_process
525
+ end
526
+
527
+ unless killed
528
+ RunLoop::ProcessTerminator.new(pid, 'KILL', process_name, kill_options)
529
+ end
530
+ end
531
+ end
532
+
533
+ # Returns the current simulator name.
534
+ #
535
+ # @return [String] A String suitable for searching for a pid, quitting, or
536
+ # launching the current simulator.
537
+ def sim_name
538
+ @sim_name ||= lambda {
539
+ if xcode.version_gte_7?
540
+ "Simulator"
541
+ else
542
+ "iOS Simulator"
543
+ end
544
+ }.call
545
+ end
546
+
547
+ # @!visibility private
548
+ # Returns the path to the current simulator.
549
+ #
550
+ # @return [String] The path to the simulator app for the current version of
551
+ # Xcode.
552
+ def sim_app_path
553
+ @sim_app_path ||= lambda {
554
+ dev_dir = xcode.developer_dir
555
+ if xcode.version_gte_7?
556
+ "#{dev_dir}/Applications/Simulator.app"
557
+ else
558
+ "#{dev_dir}/Applications/iOS Simulator.app"
559
+ end
560
+ }.call
561
+ end
562
+
563
+ # @!visibility private
564
+ # Returns the current Simulator pid.
565
+ #
566
+ # @note Will only search for the current Xcode simulator.
567
+ #
568
+ # @return [Integer, nil] The pid as a String or nil if no process is found.
569
+ def running_simulator_pid
570
+ process_name = "MacOS/#{sim_name}"
571
+
572
+ args = ["ps", "x", "-o", "pid,command"]
573
+ hash = xcrun.run_command_in_context(args)
574
+
575
+ exit_status = hash[:exit_status]
576
+ if exit_status != 0
577
+ raise RuntimeError,
578
+ %Q{Could not find the pid of #{sim_name} with:
579
+
580
+ #{args.join(" ")}
581
+
582
+ Command exited with status #{exit_status}
583
+ Message: '#{hash[:out]}'
584
+ }
585
+ end
586
+
587
+ if hash[:out].nil? || hash[:out] == ""
588
+ raise RuntimeError,
589
+ %Q{Could not find the pid of #{sim_name} with:
590
+
591
+ #{args.join(" ")}
592
+
593
+ Command had no output
594
+ }
595
+ end
596
+
597
+ lines = hash[:out].split("\n")
598
+
599
+ match = lines.detect do |line|
600
+ line[/#{process_name}/, 0]
601
+ end
602
+
603
+ return nil if match.nil?
604
+
605
+ match.split(" ").first.to_i
606
+ end
607
+
608
+ # @!visibility private
609
+ def install_app_with_simctl
610
+ launch_simulator
611
+
612
+ args = ['simctl', 'install', device.udid, app.path]
613
+ timeout = DEFAULT_OPTIONS[:install_app_timeout]
614
+ xcrun.run_command_in_context(args, log_cmd: true, timeout: timeout)
615
+
616
+ device.simulator_wait_for_stable_state
617
+ installed_app_bundle_dir
618
+ end
619
+
620
+ # @!visibility private
621
+ def launch_app_with_simctl
622
+ args = ['simctl', 'launch', device.udid, app.bundle_identifier]
623
+ timeout = DEFAULT_OPTIONS[:launch_app_timeout]
624
+ xcrun.run_command_in_context(args, log_cmd: true, timeout: timeout)
625
+ end
626
+
627
+ # @!visibility private
628
+ def handle_failed_app_launch(hash, try, tries, wait_time)
629
+ out = hash[:out]
630
+ RunLoop.log_debug("Failed to launch app on try #{try + 1} of #{tries}.")
631
+ out.split($-0).each do |line|
632
+ RunLoop.log_debug(" #{line}")
633
+ end
634
+ # If we timed out on the launch, the CoreSimulator processes are quit
635
+ # (see above). If at all possible, we want to avoid terminating
636
+ # CoreSimulatorService, because it takes a long time to launch.
637
+ sleep(wait_time) if wait_time > 0
638
+
639
+ out
640
+ end
641
+
642
+ # @!visibility private
643
+ def attempt_to_launch_app_with_simctl
644
+ begin
645
+ hash = launch_app_with_simctl
646
+ rescue RunLoop::Xcrun::TimeoutError => e
647
+ hash = {
648
+ :exit_status => 1,
649
+ :out => e.message
650
+ }
651
+ # Simulator is probably in a bad state. Terminates the
652
+ # CoreSimulatorService. Restarting this service is expensive!
653
+ RunLoop::CoreSimulator.terminate_core_simulator_processes
654
+ Kernel.sleep(0.5)
655
+ launch_simulator
656
+ end
657
+ hash
658
+ end
659
+
660
+ # Required for support of iOS 7 CoreSimulators. Can be removed when
661
+ # Xcode support is dropped.
662
+ def sdk_gte_8?
663
+ device.version >= RunLoop::Version.new('8.0')
664
+ end
665
+
666
+ # The data directory for the the device.
667
+ #
668
+ # ~/Library/Developer/CoreSimulator/Devices/<UDID>/data
669
+ def device_data_dir
670
+ @device_data_dir ||= File.join(CORE_SIMULATOR_DEVICE_DIR, device.udid, 'data')
671
+ end
672
+
673
+ # The applications directory for the device.
674
+ #
675
+ # ~/Library/Developer/CoreSimulator/Devices/<UDID>/Containers/Bundle/Application
676
+ def device_applications_dir
677
+ @device_app_dir ||= lambda do
678
+ if sdk_gte_8?
679
+ File.join(device_data_dir, 'Containers', 'Bundle', 'Application')
680
+ else
681
+ File.join(device_data_dir, 'Applications')
682
+ end
683
+ end.call
684
+ end
685
+
686
+ # The sandbox directory for the app.
687
+ #
688
+ # ~/Library/Developer/CoreSimulator/Devices/<UDID>/Containers/Data/Application
689
+ #
690
+ # Contains Library, Documents, and tmp directories.
691
+ def app_sandbox_dir
692
+ app_install_dir = installed_app_bundle_dir
693
+ return nil if app_install_dir.nil?
694
+ if sdk_gte_8?
695
+ app_sandbox_dir_sdk_gte_8
696
+ else
697
+ app_install_dir
698
+ end
699
+ end
700
+
701
+ def app_sandbox_dir_sdk_gte_8
702
+ containers_data_dir = File.join(device_data_dir, 'Containers', 'Data', 'Application')
703
+ apps = Dir.glob("#{containers_data_dir}/**/#{METADATA_PLIST}")
704
+ match = apps.find do |metadata_plist|
705
+ pbuddy.plist_read('MCMMetadataIdentifier', metadata_plist) == app.bundle_identifier
706
+ end
707
+ if match
708
+ File.dirname(match)
709
+ else
710
+ nil
711
+ end
712
+ end
713
+
714
+ # The Library directory in the sandbox.
715
+ def app_library_dir
716
+ base_dir = app_sandbox_dir
717
+ if base_dir.nil?
718
+ nil
719
+ else
720
+ File.join(base_dir, 'Library')
721
+ end
722
+ end
723
+
724
+ # The Library/Preferences directory in the sandbox.
725
+ def app_library_preferences_dir
726
+ base_dir = app_library_dir
727
+ if base_dir.nil?
728
+ nil
729
+ else
730
+ File.join(base_dir, 'Preferences')
731
+ end
732
+ end
733
+
734
+ # The Documents directory in the sandbox.
735
+ def app_documents_dir
736
+ base_dir = app_sandbox_dir
737
+ if base_dir.nil?
738
+ nil
739
+ else
740
+ File.join(base_dir, 'Documents')
741
+ end
742
+ end
743
+
744
+ # The tmp directory in the sandbox.
745
+ def app_tmp_dir
746
+ base_dir = app_sandbox_dir
747
+ if base_dir.nil?
748
+ nil
749
+ else
750
+ File.join(base_dir, 'tmp')
751
+ end
752
+ end
753
+
754
+ # A cache of installed apps on the device.
755
+ def device_caches_dir
756
+ @device_caches_dir ||= File.join(device_data_dir, 'Library', 'Caches')
757
+ end
758
+
759
+ # Required after when installing and uninstalling.
760
+ def clear_device_launch_csstore
761
+ glob = File.join(device_caches_dir, "com.apple.LaunchServices-*.csstore")
762
+ Dir.glob(glob) do | ccstore |
763
+ FileUtils.rm_f ccstore
764
+ end
765
+ end
766
+
767
+ # The sha1 of the installed app.
768
+ def installed_app_sha1
769
+ installed_bundle = installed_app_bundle_dir
770
+ if installed_bundle
771
+ RunLoop::Directory.directory_digest(installed_bundle)
772
+ else
773
+ nil
774
+ end
775
+ end
776
+
777
+ # Is the app that is install the same as the one we have in hand?
778
+ def same_sha1_as_installed?
779
+ app.sha1 == installed_app_sha1
780
+ end
781
+
782
+ # Returns the path to the installed app bundle directory (.app).
783
+ #
784
+ # If this method returns nil, the app is not installed.
785
+ def installed_app_bundle_dir
786
+ sim_app_dir = device_applications_dir
787
+ return nil if !File.exist?(sim_app_dir)
788
+
789
+ app_bundle_dir = Dir.glob("#{sim_app_dir}/**/*.app").find do |path|
790
+ RunLoop::App.new(path).bundle_identifier == app.bundle_identifier
791
+ end
792
+
793
+ app_bundle_dir = ensure_complete_app_installation(app_bundle_dir)
794
+
795
+ app_bundle_dir
796
+ end
797
+
798
+ # Cleans up bad installations of an app. For unknown reasons, an app bundle
799
+ # can exist, but be unrecognized by CoreSimulator. If we detect a case like
800
+ # this, we need to clean up the installation.
801
+ def ensure_complete_app_installation(app_bundle_dir)
802
+ return nil if app_bundle_dir.nil?
803
+ return app_bundle_dir if complete_app_install?(app_bundle_dir)
804
+
805
+ # Remove the directory that contains the app bundle
806
+ base_dir = File.dirname(app_bundle_dir)
807
+ FileUtils.rm_rf(base_dir)
808
+
809
+ # Clean up Containers/Data/Application
810
+ remove_stale_data_containers
811
+
812
+ nil
813
+ end
814
+
815
+ # Detect an incomplete app installation.
816
+ def complete_app_install?(app_bundle_dir)
817
+ base_dir = File.dirname(app_bundle_dir)
818
+ plist = File.join(base_dir, METADATA_PLIST)
819
+ File.exist?(plist)
820
+ end
821
+
822
+ # Remove stale data directories that might have appeared as a result of an
823
+ # incomplete app installation.
824
+ # See #ensure_complete_app_installation
825
+ def remove_stale_data_containers
826
+ containers_data_dir = File.join(device_data_dir, "Containers", "Data", "Application")
827
+ apps = Dir.glob("#{containers_data_dir}/**/#{METADATA_PLIST}")
828
+ apps.each do |metadata_plist|
829
+ if pbuddy.plist_read("MCMMetadataIdentifier", metadata_plist) == app.bundle_identifier
830
+ FileUtils.rm_rf(File.dirname(metadata_plist))
831
+ end
832
+ end
833
+ end
834
+
835
+ # 1. Does nothing if the app is not installed.
836
+ # 2. Does nothing if the app the same as the app that is installed
837
+ # 3. Installs app if it is different from the installed app
838
+ def ensure_app_same
839
+ installed_app_bundle = installed_app_bundle_dir
840
+
841
+ if !installed_app_bundle
842
+ RunLoop.log_debug("App: #{app} is not installed")
843
+ return true
844
+ end
845
+
846
+ installed_sha = installed_app_sha1
847
+ app_sha = app.sha1
848
+
849
+ if installed_sha == app_sha
850
+ RunLoop.log_debug("Installed app is the same as #{app}")
851
+ return true
852
+ end
853
+
854
+ RunLoop.log_debug("The app you are testing is not the same as the app that is installed.")
855
+ RunLoop.log_debug(" Installed app SHA: #{installed_sha}")
856
+ RunLoop.log_debug(" App to launch SHA: #{app_sha}")
857
+ RunLoop.log_debug("Will install #{app}")
858
+
859
+ FileUtils.rm_rf installed_app_bundle
860
+ RunLoop.log_debug('Deleted the existing app')
861
+
862
+ directory = File.expand_path(File.join(installed_app_bundle, '..'))
863
+ bundle_name = File.basename(app.path)
864
+ target = File.join(directory, bundle_name)
865
+
866
+ args = ['ditto', app.path, target]
867
+ xcrun.run_command_in_context(args, log_cmd: true)
868
+
869
+ RunLoop.log_debug("Installed #{app} on CoreSimulator #{device.udid}")
870
+
871
+ clear_device_launch_csstore
872
+
873
+ true
874
+ end
875
+
876
+ # Shared tasks across CoreSimulators iOS 7 and > iOS 7
877
+ def reset_app_sandbox_internal_shared
878
+ [app_documents_dir, app_tmp_dir].each do |dir|
879
+ FileUtils.rm_rf dir
880
+ FileUtils.mkdir dir
881
+ end
882
+ end
883
+
884
+ # @!visibility private
885
+ def reset_app_sandbox_internal_sdk_gte_8
886
+ lib_dir = app_library_dir
887
+ RunLoop::Directory.recursive_glob_for_entries(lib_dir).each do |entry|
888
+ if entry.include?('Preferences')
889
+ # nop
890
+ else
891
+ if File.exist?(entry)
892
+ FileUtils.rm_rf(entry)
893
+ end
894
+ end
895
+ end
896
+
897
+ prefs_dir = app_library_preferences_dir
898
+ protected = ['com.apple.UIAutomation.plist',
899
+ 'com.apple.UIAutomationPlugIn.plist']
900
+ RunLoop::Directory.recursive_glob_for_entries(prefs_dir).each do |entry|
901
+ unless protected.include?(File.basename(entry))
902
+ if File.exist?(entry)
903
+ FileUtils.rm_rf entry
904
+ end
905
+ end
906
+ end
907
+ end
908
+
909
+ # @!visibility private
910
+ def reset_app_sandbox_internal_sdk_lt_8
911
+ prefs_dir = app_library_preferences_dir
912
+ RunLoop::Directory.recursive_glob_for_entries(prefs_dir).each do |entry|
913
+ if entry.end_with?('.GlobalPreferences.plist') ||
914
+ entry.end_with?('com.apple.PeoplePicker.plist')
915
+ # nop
916
+ else
917
+ if File.exist?(entry)
918
+ FileUtils.rm_rf entry
919
+ end
920
+ end
921
+ end
922
+
923
+ # app preferences lives in device Library/Preferences
924
+ device_prefs_dir = File.join(app_sandbox_dir, 'Library', 'Preferences')
925
+ app_prefs_plist = File.join(device_prefs_dir, "#{app.bundle_identifier}.plist")
926
+ if File.exist?(app_prefs_plist)
927
+ FileUtils.rm_rf(app_prefs_plist)
928
+ end
929
+ end
930
+
931
+ # @!visibility private
932
+ def reset_app_sandbox_internal
933
+ reset_app_sandbox_internal_shared
934
+
935
+ if sdk_gte_8?
936
+ reset_app_sandbox_internal_sdk_gte_8
937
+ else
938
+ reset_app_sandbox_internal_sdk_lt_8
939
+ end
940
+ end
941
+
942
+ # Not yet. Failing on Travis and this is not a feature yet.
943
+ #
944
+ # There is a spec that has been commented out.
945
+ # @!visibility private
946
+ # TODO Command line tool
947
+ # def app_uia_crash_logs
948
+ # base_dir = app_library_dir
949
+ # if base_dir.nil?
950
+ # nil
951
+ # else
952
+ # dir = File.join(base_dir, 'CrashReporter', 'UIALogs')
953
+ # if Dir.exist?(dir)
954
+ # Dir.glob("#{dir}/*.plist")
955
+ # else
956
+ # nil
957
+ # end
958
+ # end
959
+ # end
960
+ end