run_loop_tcc 2.1.3

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.
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,902 @@
1
+ require 'fileutils'
2
+ require 'tmpdir'
3
+ require 'timeout'
4
+ require 'json'
5
+ require 'open3'
6
+ require 'erb'
7
+ require 'ap'
8
+
9
+ module RunLoop
10
+
11
+ module Core
12
+
13
+ include RunLoop::Regex
14
+
15
+ START_DELIMITER = "OUTPUT_JSON:\n"
16
+ END_DELIMITER="\nEND_OUTPUT"
17
+
18
+ SCRIPTS = {
19
+ :dismiss => 'run_dismiss_location.js',
20
+ :run_loop_host => 'run_loop_host.js',
21
+ :run_loop_fast_uia => 'run_loop_fast_uia.js',
22
+ :run_loop_shared_element => 'run_loop_shared_element.js',
23
+ :run_loop_basic => 'run_loop_basic.js'
24
+ }
25
+
26
+ SCRIPTS_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'scripts'))
27
+ READ_SCRIPT_PATH = File.join(SCRIPTS_PATH, 'read-cmd.sh')
28
+ TIMEOUT_SCRIPT_PATH = File.join(SCRIPTS_PATH, 'timeout3')
29
+
30
+ def self.log_run_loop_options(options, xcode)
31
+ return unless RunLoop::Environment.debug?
32
+ # Ignore :sim_control b/c it is a ruby object; printing is not useful.
33
+ ignored_keys = [:sim_control]
34
+ options_to_log = {}
35
+ options.each_pair do |key, value|
36
+ next if ignored_keys.include?(key)
37
+ options_to_log[key] = value
38
+ end
39
+ # Objects that override '==' cannot be printed by awesome_print
40
+ # https://github.com/michaeldv/awesome_print/issues/154
41
+ # RunLoop::Version overrides '=='
42
+ options_to_log[:xcode] = xcode.version.to_s
43
+ options_to_log[:xcode_path] = xcode.developer_dir
44
+ message = options_to_log.ai({:sort_keys => true})
45
+ logger = options[:logger]
46
+ RunLoop::Logging.log_debug(logger, "\n" + message)
47
+ end
48
+
49
+ def self.script_for_key(key)
50
+ if SCRIPTS[key].nil?
51
+ return nil
52
+ end
53
+ SCRIPTS[key]
54
+ end
55
+
56
+ # @!visibility private
57
+ # This is the entry point for UIAutomation.
58
+ def self.run_with_options(options)
59
+ before = Time.now
60
+
61
+ self.prepare(options)
62
+
63
+ logger = options[:logger]
64
+ simctl = options[:sim_control] || options[:simctl] || RunLoop::Simctl.new
65
+ xcode = options[:xcode] || RunLoop::Xcode.new
66
+ instruments = options[:instruments] || RunLoop::Instruments.new
67
+
68
+ if xcode.version_gte_8?
69
+ raise %Q[
70
+ UIAutomation is not available on Xcode >= 8.*.
71
+
72
+ We are in the process of updating Calabash to use our new tool: DeviceAgent.
73
+
74
+ We will track progress in this forum post:
75
+
76
+ https://groups.google.com/forum/#!topic/calabash-ios/g34znf0LnE4
77
+
78
+ For now, testing with Xcode 8 is not supported.
79
+
80
+ Thank you for your patience.
81
+ ]
82
+ end
83
+
84
+ # Device under test: DUT
85
+ device = RunLoop::Device.detect_device(options, xcode, simctl, instruments)
86
+
87
+ # App under test: AUT
88
+ app_details = RunLoop::DetectAUT.detect_app_under_test(options)
89
+
90
+ # Find the script to pass to instruments and the strategy to communicate
91
+ # with UIAutomation.
92
+ script_n_strategy = self.detect_instruments_script_and_strategy(options,
93
+ device,
94
+ xcode)
95
+ instruments_script = script_n_strategy[:script]
96
+ uia_strategy = script_n_strategy[:strategy]
97
+
98
+ # The app life cycle reset options.
99
+ reset_options = self.detect_reset_options(options)
100
+
101
+ instruments.kill_instruments(xcode)
102
+
103
+ timeout = options[:timeout] || 30
104
+
105
+ results_dir = options[:results_dir] || RunLoop::DotDir.make_results_dir
106
+ results_dir_trace = File.join(results_dir, 'trace')
107
+ FileUtils.mkdir_p(results_dir_trace)
108
+
109
+ dependencies = options[:dependencies] || []
110
+ dependencies << File.join(SCRIPTS_PATH, 'calabash_script_uia.js')
111
+ dependencies.each do |dep|
112
+ FileUtils.cp(dep, results_dir)
113
+ end
114
+
115
+ script = File.join(results_dir, '_run_loop.js')
116
+
117
+ javascript = UIAScriptTemplate.new(SCRIPTS_PATH, instruments_script).result
118
+ UIAScriptTemplate.sub_path_var!(javascript, results_dir)
119
+ UIAScriptTemplate.sub_read_script_path_var!(javascript, READ_SCRIPT_PATH)
120
+ UIAScriptTemplate.sub_timeout_script_path_var!(javascript, TIMEOUT_SCRIPT_PATH)
121
+
122
+ # Using a :no_* option is confusing.
123
+ # TODO Replace :no_flush with :flush_uia_logs; it should default to true
124
+ if RunLoop::Environment.xtc?
125
+ UIAScriptTemplate.sub_mode_var!(javascript, "FLUSH") unless options[:no_flush]
126
+ else
127
+ if self.detect_flush_uia_log_option(options)
128
+ UIAScriptTemplate.sub_flush_uia_logs_var!(javascript, "FLUSH_LOGS")
129
+ end
130
+ end
131
+
132
+ repl_path = File.join(results_dir, 'repl-cmd.pipe')
133
+ FileUtils.rm_f(repl_path)
134
+
135
+ if uia_strategy == :host
136
+ create_uia_pipe(repl_path)
137
+ else
138
+ FileUtils.touch repl_path
139
+ end
140
+
141
+ RunLoop::HostCache.default.clear unless RunLoop::Environment.xtc?
142
+
143
+ cal_script = File.join(SCRIPTS_PATH, 'calabash_script_uia.js')
144
+ File.open(script, 'w') do |file|
145
+ if include_calabash_script?(options)
146
+ file.puts IO.read(cal_script)
147
+ end
148
+ file.puts javascript
149
+ end
150
+
151
+ args = options.fetch(:args, [])
152
+
153
+ log_file = options[:log_path] || File.join(results_dir, 'run_loop.out')
154
+
155
+ discovered_options =
156
+ {
157
+ :udid => device.udid,
158
+ :device => device,
159
+ :results_dir_trace => results_dir_trace,
160
+ :bundle_id => app_details[:bundle_id],
161
+ :app => app_details[:app] || app_details[:bundle_id],
162
+ :results_dir => results_dir,
163
+ :script => script,
164
+ :log_file => log_file,
165
+ :args => args,
166
+ :uia_strategy => uia_strategy,
167
+ :base_script => instruments_script
168
+ }
169
+ merged_options = options.merge(discovered_options)
170
+
171
+ if device.simulator?
172
+ if !app_details[:app]
173
+ raise %Q[
174
+
175
+ Invalid APP, APP_BUNDLE_PATH, or BUNDLE_ID detected.
176
+
177
+ The following information was detected from the environment:
178
+
179
+ APP='#{ENV["APP"]}'
180
+ APP_BUNDLE_PATH='#{ENV["APP_BUNDLE_PATH"]}'
181
+ BUNDLE_ID='#{ENV["BUNDLE_ID"]}'
182
+
183
+
184
+ It looks like you are trying to launch an app on a simulator using a bundle
185
+ identifier or you have incorrectly set the APP variable to an app bundle that
186
+ does not exist.
187
+
188
+ If you are trying to launch a test against a physical device, set the DEVICE_TARGET
189
+ variable to the UDID of your device an APP to a bundle identifier or a path to
190
+ an .ipa.
191
+
192
+ # com.example.MyApp must be installed on the target device
193
+ $ APP=com.example.MyApp DEVICE_TARGET="John's iPhone" cucumber
194
+
195
+ If you are trying to launch against a simulator and you encounter this error, it
196
+ means that the APP variable is pointing to a .app that does not exist.
197
+
198
+ ]
199
+ end
200
+ self.prepare_simulator(app_details[:app], device, xcode, simctl, reset_options)
201
+ end
202
+
203
+ self.log_run_loop_options(merged_options, xcode)
204
+
205
+ automation_template = automation_template(instruments)
206
+
207
+ RunLoop::Logging.log_header(logger, "Starting on #{device.name} App: #{app_details[:bundle_id]}")
208
+
209
+ pid = instruments.spawn(automation_template, merged_options, log_file)
210
+
211
+ File.open(File.join(results_dir, 'run_loop.pid'), 'w') do |f|
212
+ f.write pid
213
+ end
214
+
215
+ run_loop = {
216
+ :pid => pid,
217
+ :index => 1,
218
+ :uia_strategy => uia_strategy,
219
+ :udid => device.udid,
220
+ :app => app_details[:bundle_id],
221
+ :repl_path => repl_path,
222
+ :log_file => log_file,
223
+ :results_dir => results_dir
224
+ }
225
+
226
+ uia_timeout = options[:uia_timeout] || RunLoop::Environment.uia_timeout || 10
227
+
228
+ RunLoop::Logging.log_debug(logger, "Preparation took #{Time.now-before} seconds")
229
+
230
+ before_instruments_launch = Time.now
231
+
232
+ fifo_retry_on = [
233
+ RunLoop::Fifo::NoReaderConfiguredError,
234
+ RunLoop::Fifo::WriteTimedOut
235
+ ]
236
+
237
+ begin
238
+
239
+ if options[:validate_channel]
240
+ options[:validate_channel].call(run_loop, 0, uia_timeout)
241
+ else
242
+
243
+ cmd = "UIALogger.logMessage('Listening for run loop commands')"
244
+
245
+ begin
246
+
247
+ fifo_timeout = options[:fifo_timeout] || 30
248
+ RunLoop::Fifo.write(repl_path, "0:#{cmd}", timeout: fifo_timeout)
249
+
250
+ rescue *fifo_retry_on => e
251
+
252
+ message = "Error while writing to fifo. #{e}"
253
+ RunLoop::Logging.log_debug(logger, message)
254
+ raise RunLoop::TimeoutError.new(message)
255
+
256
+ end
257
+
258
+ Timeout::timeout(timeout, RunLoop::TimeoutError) do
259
+ read_response(run_loop, 0, uia_timeout)
260
+ end
261
+
262
+ end
263
+ rescue RunLoop::TimeoutError => e
264
+ RunLoop::Logging.log_debug(logger, "Failed to launch. #{e}: #{e && e.message}")
265
+
266
+ message = %Q(
267
+
268
+ "Timed out waiting for UIAutomation run-loop #{e}.
269
+
270
+ Logfile: #{log_file}
271
+
272
+ #{File.read(log_file)}
273
+
274
+ )
275
+ raise RunLoop::TimeoutError, message
276
+ end
277
+
278
+ RunLoop::Logging.log_debug(logger, "Launching took #{Time.now-before_instruments_launch} seconds")
279
+
280
+ dylib_path = self.dylib_path_from_options(merged_options)
281
+
282
+ if dylib_path
283
+ if device.physical_device?
284
+ raise RuntimeError, "Injecting a dylib is not supported when targeting a device"
285
+ end
286
+
287
+ app = app_details[:app]
288
+ lldb = RunLoop::DylibInjector.new(app.executable_name, dylib_path)
289
+ lldb.retriable_inject_dylib
290
+ end
291
+
292
+ RunLoop.log_debug("It took #{Time.now - before} seconds to launch the app")
293
+ run_loop
294
+ end
295
+
296
+ # @!visibility private
297
+ # Usually we include CalabashScript to ease uia automation.
298
+ # However in certain scenarios we don't load it since
299
+ # it slows down the UIAutomation initialization process
300
+ # occasionally causing privacy/security dialogs not to be automated.
301
+ #
302
+ # @return {boolean} true if CalabashScript should be loaded
303
+ def self.include_calabash_script?(options)
304
+
305
+ if (options[:include_calabash_script] == false) || options[:dismiss_immediate_dialogs]
306
+ return false
307
+ end
308
+ if Core.script_for_key(:run_loop_basic) == options[:script]
309
+ return options[:include_calabash_script]
310
+ end
311
+ true
312
+ end
313
+
314
+ # Extracts the value of :inject_dylib from options Hash.
315
+ # @param options [Hash] arguments passed to {RunLoop.run}
316
+ # @return [String, nil] If the options contains :inject_dylibs and it is a
317
+ # path to a dylib that exists, return the path. Otherwise return nil or
318
+ # raise an error.
319
+ # @raise [RuntimeError] If :inject_dylib points to a path that does not exist.
320
+ # @raise [ArgumentError] If :inject_dylib is not a String.
321
+ def self.dylib_path_from_options(options)
322
+ inject_dylib = options.fetch(:inject_dylib, nil)
323
+ return nil if inject_dylib.nil?
324
+ unless inject_dylib.is_a? String
325
+ raise ArgumentError, "Expected :inject_dylib to be a path to a dylib, but found '#{inject_dylib}'"
326
+ end
327
+ dylib_path = File.expand_path(inject_dylib)
328
+ unless File.exist?(dylib_path)
329
+ raise "Cannot load dylib. The file '#{dylib_path}' does not exist."
330
+ end
331
+ dylib_path
332
+ end
333
+
334
+ # Returns the a default simulator to target. This default needs to be one
335
+ # that installed by default in the current Xcode version.
336
+ #
337
+ # For historical reasons, the most recent non-64b SDK should be used.
338
+ #
339
+ # @param [RunLoop::Xcode] xcode Used to detect the current xcode
340
+ # version.
341
+ def self.default_simulator(xcode=RunLoop::Xcode.new)
342
+
343
+ if xcode.version_gte_8?
344
+ "iPhone 6s (10.0)"
345
+ elsif xcode.version_gte_73?
346
+ "iPhone 6s (9.3)"
347
+ elsif xcode.version_gte_72?
348
+ "iPhone 6s (9.2)"
349
+ elsif xcode.version_gte_71?
350
+ "iPhone 6s (9.1)"
351
+ elsif xcode.version_gte_7?
352
+ "iPhone 5s (9.0)"
353
+ elsif xcode.version_gte_64?
354
+ "iPhone 5s (8.4 Simulator)"
355
+ elsif xcode.version_gte_63?
356
+ "iPhone 5s (8.3 Simulator)"
357
+ elsif xcode.version_gte_62?
358
+ "iPhone 5s (8.2 Simulator)"
359
+ elsif xcode.version_gte_61?
360
+ "iPhone 5s (8.1 Simulator)"
361
+ else
362
+ "iPhone 5s (8.0 Simulator)"
363
+ end
364
+ end
365
+
366
+
367
+ def self.create_uia_pipe(repl_path)
368
+ begin
369
+ Timeout::timeout(5, RunLoop::TimeoutError) do
370
+ loop do
371
+ begin
372
+ FileUtils.rm_f(repl_path)
373
+ return repl_path if system(%Q[mkfifo "#{repl_path}"])
374
+ rescue Errno::EINTR => _
375
+ sleep(0.1)
376
+ end
377
+ end
378
+ end
379
+ rescue RunLoop::TimeoutError => _
380
+ raise RunLoop::TimeoutError, 'Unable to create pipe (mkfifo failed)'
381
+ end
382
+ end
383
+
384
+ def self.jruby?
385
+ RUBY_PLATFORM == 'java'
386
+ end
387
+
388
+ def self.write_request(run_loop, cmd, logger=nil)
389
+ repl_path = run_loop[:repl_path]
390
+ index = run_loop[:index]
391
+ cmd_str = "#{index}:#{escape_host_command(cmd)}"
392
+ RunLoop::Logging.log_debug(logger, cmd_str)
393
+ write_succeeded = false
394
+ 2.times do |i|
395
+ RunLoop::Logging.log_debug(logger, "Trying write of command #{cmd_str} at index #{index}")
396
+ begin
397
+ RunLoop::Fifo.write(repl_path, cmd_str)
398
+ write_succeeded = validate_index_written(run_loop, index, logger)
399
+ rescue RunLoop::Fifo::NoReaderConfiguredError,
400
+ RunLoop::Fifo::WriteTimedOut => e
401
+ RunLoop::Logging.log_debug(logger, "Error while writing command (retry count #{i}). #{e}")
402
+ end
403
+ break if write_succeeded
404
+ end
405
+ unless write_succeeded
406
+ RunLoop::Logging.log_debug(logger, 'Failing...Raising RunLoop::WriteFailedError')
407
+ raise RunLoop::WriteFailedError.new("Trying write of command #{cmd_str} at index #{index}")
408
+ end
409
+ run_loop[:index] = index + 1
410
+ RunLoop::HostCache.default.write(run_loop) unless RunLoop::Environment.xtc?
411
+ index
412
+ end
413
+
414
+ def self.validate_index_written(run_loop, index, logger)
415
+ begin
416
+ Timeout::timeout(10, RunLoop::TimeoutError) do
417
+ Core.read_response(run_loop, index, 10, 'last_index')
418
+ end
419
+ RunLoop::Logging.log_debug(logger, "validate index written for index #{index} ok")
420
+ return true
421
+ rescue RunLoop::TimeoutError => _
422
+ RunLoop::Logging.log_debug(logger, "validate index written for index #{index} failed. Retrying.")
423
+ return false
424
+ end
425
+ end
426
+
427
+ def self.escape_host_command(cmd)
428
+ backquote = "\\"
429
+ cmd.gsub(backquote,backquote*4)
430
+ end
431
+
432
+ def self.log_instruments_error(msg)
433
+ $stderr.puts "\033[31m\n\n*** #{msg} ***\n\n\033[0m"
434
+ $stderr.flush
435
+ end
436
+
437
+ def self.read_response(run_loop, expected_index, empty_file_timeout=10, search_for_property='index')
438
+ debug_read = RunLoop::Environment.debug_read?
439
+
440
+ log_file = run_loop[:log_file]
441
+ initial_offset = run_loop[:initial_offset] || 0
442
+ offset = initial_offset
443
+
444
+ result = nil
445
+ loop do
446
+ unless File.exist?(log_file) && File.size?(log_file)
447
+ sleep(0.2)
448
+ next
449
+ end
450
+
451
+ size = File.size(log_file)
452
+ output = File.read(log_file, size-offset, offset)
453
+
454
+ if /AXError: Could not auto-register for pid status change/.match(output)
455
+ if /kAXErrorServerNotFound/.match(output)
456
+ self.log_instruments_error('Accessibility is not enabled on device/simulator, please enable it.')
457
+ end
458
+ raise RunLoop::TimeoutError.new('AXError: Could not auto-register for pid status change')
459
+ end
460
+
461
+ if /Automation Instrument ran into an exception/.match(output)
462
+ raise RunLoop::TimeoutError.new('Exception while running script')
463
+ end
464
+
465
+ if /FBSOpenApplicationErrorDomain error/.match(output)
466
+ msg = "Instruments failed to launch app: 'FBSOpenApplicationErrorDomain error 8"
467
+ if RunLoop::Environment.debug?
468
+ self.log_instruments_error(msg)
469
+ end
470
+ raise RunLoop::TimeoutError.new(msg)
471
+ end
472
+
473
+ if /Error: Script threw an uncaught JavaScript error: unknown JavaScript exception/.match(output)
474
+ msg = "Instruments failed to launch: because of an unknown JavaScript exception"
475
+ if RunLoop::Environment.debug?
476
+ self.log_instruments_error(msg)
477
+ end
478
+ raise RunLoop::TimeoutError.new(msg)
479
+ end
480
+
481
+ index_if_found = output.index(START_DELIMITER)
482
+ if debug_read
483
+ puts output.gsub('*', '')
484
+ puts "Size #{size}"
485
+ puts "offset #{offset}"
486
+ puts "index_of #{START_DELIMITER}: #{index_if_found}"
487
+ end
488
+
489
+ if index_if_found
490
+
491
+ offset = offset + index_if_found
492
+ rest = output[index_if_found+START_DELIMITER.size..output.length]
493
+ index_of_json = rest.index("}#{END_DELIMITER}")
494
+
495
+ if index_of_json.nil?
496
+ #Wait for rest of json
497
+ sleep(0.1)
498
+ next
499
+ end
500
+
501
+ json = rest[0..index_of_json]
502
+
503
+
504
+ if debug_read
505
+ puts "Index #{index_if_found}, Size: #{size} Offset #{offset}"
506
+
507
+ puts ("parse #{json}")
508
+ end
509
+
510
+ offset = offset + json.size
511
+ parsed_result = JSON.parse(json)
512
+ if debug_read
513
+ p parsed_result
514
+ end
515
+ json_index_if_present = parsed_result[search_for_property]
516
+ if json_index_if_present && json_index_if_present == expected_index
517
+ result = parsed_result
518
+ break
519
+ end
520
+ else
521
+ sleep(0.1)
522
+ end
523
+ end
524
+
525
+ run_loop[:initial_offset] = offset
526
+ RunLoop::HostCache.default.write(run_loop) unless RunLoop::Environment.xtc?
527
+ result
528
+ end
529
+
530
+ def self.automation_template(instruments, candidate=RunLoop::Environment.trace_template)
531
+ unless candidate && File.exist?(candidate)
532
+ candidate = default_tracetemplate(instruments)
533
+ end
534
+ candidate
535
+ end
536
+
537
+ def self.default_tracetemplate(instruments=RunLoop::Instruments.new)
538
+
539
+ templates = instruments.templates
540
+
541
+ # xcrun instruments -s templates
542
+ # Xcode >= 6 will return known, Apple defined tracetemplates as names
543
+ # e.g. Automation, Zombies, Allocations
544
+ #
545
+ # Xcode < 6 will return known, Apple defined tracetemplates as paths.
546
+ #
547
+ # Xcode 6 Beta versions also return paths, but revert to 'normal'
548
+ # behavior when GM is released.
549
+ #
550
+ # Xcode 7 Beta versions appear to behavior like Xcode 6 Beta versions.
551
+ template = templates.find { |name| name == 'Automation' }
552
+ return template if template
553
+
554
+ candidate = templates.find do |path|
555
+ path =~ /\/Automation.tracetemplate/ and path =~ /Xcode/
556
+ end
557
+
558
+ if !candidate.nil?
559
+ return candidate.tr("\"", '').strip
560
+ end
561
+
562
+ message = ['Expected instruments to report an Automation tracetemplate.',
563
+ 'Please report this as bug: https://github.com/calabash/run_loop/issues',
564
+ "In the bug report, include the output of:\n",
565
+ '$ xcrun xcodebuild -version',
566
+ "$ xcrun instruments -s templates\n"]
567
+ raise message.join("\n")
568
+ end
569
+
570
+ # @deprecated 2.1.0
571
+ # Replaced with Device.detect_physical_device_on_usb
572
+ def self.detect_connected_device
573
+ begin
574
+ Timeout::timeout(1, RunLoop::TimeoutError) do
575
+ return `#{File.join(SCRIPTS_PATH, 'udidetect')}`.chomp
576
+ end
577
+ rescue RunLoop::TimeoutError => _
578
+ `killall udidetect &> /dev/null`
579
+ end
580
+ nil
581
+ end
582
+
583
+ # @deprecated 2.1.0
584
+ # @!visibility private
585
+ # Are we targeting a simulator?
586
+ #
587
+ # @note The behavior of this method is different than the corresponding
588
+ # method in Calabash::Cucumber::Launcher method. If
589
+ # `:device_target => {nil | ''}`, then the calabash-ios method returns
590
+ # _false_. I am basing run-loop's behavior off the behavior in
591
+ # `self.udid_and_bundle_for_launcher`
592
+ #
593
+ # @see {Core::RunLoop.udid_and_bundle_for_launcher}
594
+ #
595
+ # @todo sim_control argument is no longer necessary and can be removed.
596
+ def self.simulator_target?(run_options, sim_control=nil)
597
+ # TODO Enable deprecation warning
598
+ # RunLoop.deprecated("2.1.0", "No replacement")
599
+ value = run_options[:device_target]
600
+
601
+ # Match the behavior of udid_and_bundle_for_launcher.
602
+ return true if value.nil? or value == ''
603
+
604
+ # 5.1 <= Xcode < 7.0
605
+ return true if value.downcase.include?('simulator')
606
+
607
+ # Not a physical device.
608
+ return false if value[DEVICE_UDID_REGEX, 0] != nil
609
+
610
+ # Check for named simulators and Xcode >= 7.0 simulators.
611
+ simctl = run_options[:sim_control] || run_options[:simctl] || RunLoop::Simctl.new
612
+ xcode = run_options[:xcode] || RunLoop::Xcode.new
613
+ simulator = simctl.simulators.find do |sim|
614
+ [
615
+ sim.instruments_identifier(xcode) == value,
616
+ sim.udid == value,
617
+ sim.name == value
618
+ ].any?
619
+ end
620
+ !simulator.nil?
621
+ end
622
+
623
+ # @!visibility private
624
+ # @deprecated 2.1.0
625
+ #
626
+ # Do not call this method.
627
+ def self.udid_and_bundle_for_launcher(device_target, options, simctl=RunLoop::Simctl.new)
628
+ RunLoop.deprecated("2.1.0", "No replacement")
629
+ xcode = RunLoop::Xcode.new
630
+
631
+ bundle_dir_or_bundle_id = options[:app] || RunLoop::Environment.bundle_id || RunLoop::Environment.path_to_app_bundle
632
+
633
+ unless bundle_dir_or_bundle_id
634
+ raise 'key :app or environment variable APP_BUNDLE_PATH, BUNDLE_ID or APP must be specified as path to app bundle (simulator) or bundle id (device)'
635
+ end
636
+
637
+ if device_target.nil? || device_target.empty? || device_target == 'simulator'
638
+ device_target = self.default_simulator(xcode)
639
+ end
640
+ udid = device_target
641
+
642
+ unless self.simulator_target?(options)
643
+ bundle_dir_or_bundle_id = options[:bundle_id] if options[:bundle_id]
644
+ end
645
+ return udid, bundle_dir_or_bundle_id
646
+ end
647
+
648
+ # @deprecated 1.0.5
649
+ def self.ensure_instruments_not_running!
650
+ RunLoop::Instruments.new.kill_instruments
651
+ end
652
+
653
+ def self.instruments_running?
654
+ RunLoop::Instruments.new.instruments_running?
655
+ end
656
+
657
+ # @deprecated 1.0.5
658
+ def self.instruments_pids
659
+ RunLoop::Instruments.new.instruments_pids
660
+ end
661
+
662
+ # @deprecated 1.0.0 replaced with Xctools#version
663
+ def self.xcode_version(xcode=RunLoop::Xcode.new)
664
+ xcode.version
665
+ end
666
+
667
+ # @deprecated since 1.0.0
668
+ # still used extensively in calabash-ios launcher
669
+ def self.above_or_eql_version?(target_version, xcode_version)
670
+ if target_version.is_a?(RunLoop::Version)
671
+ target = target_version
672
+ else
673
+ target = RunLoop::Version.new(target_version)
674
+ end
675
+
676
+ if xcode_version.is_a?(RunLoop::Version)
677
+ xcode = xcode_version
678
+ else
679
+ xcode = RunLoop::Version.new(xcode_version)
680
+ end
681
+ target >= xcode
682
+ end
683
+
684
+ # @deprecated 1.0.5
685
+ def self.pids_for_run_loop(run_loop, &block)
686
+ RunLoop::Instruments.new.instruments_pids(&block)
687
+ end
688
+
689
+ private
690
+
691
+ # @!visibility private
692
+ #
693
+ # @param [Hash] options The launch options passed to .run_with_options
694
+ def self.prepare(run_options)
695
+ RunLoop::DotDir.rotate_result_directories
696
+ RunLoop::Instruments.rotate_cache_directories
697
+ true
698
+ end
699
+
700
+ # @!visibility private
701
+ #
702
+ # @param [RunLoop::Device] device The device under test.
703
+ # @param [RunLoop::Xcode] xcode The active Xcode
704
+ def self.default_uia_strategy(device, xcode)
705
+ if xcode.version_gte_7?
706
+ :host
707
+ elsif device.physical_device? && device.version >= RunLoop::Version.new("8.0")
708
+ :host
709
+ else
710
+ :preferences
711
+ end
712
+ end
713
+
714
+ # @!visibility private
715
+ #
716
+ # @param [Hash] options The launch options passed to .run_with_options
717
+ # @param [RunLoop::Device] device The device under test.
718
+ # @param [RunLoop::Xcode] xcode The active Xcode.
719
+ def self.detect_uia_strategy(options, device, xcode)
720
+ strategy = options[:uia_strategy] || self.default_uia_strategy(device, xcode)
721
+
722
+ if ![:host, :preferences, :shared_element].include?(strategy)
723
+ raise ArgumentError,
724
+ "Invalid strategy: expected '#{strategy}' to be :host, :preferences, or :shared_element"
725
+ end
726
+ strategy
727
+ end
728
+
729
+ # @!visibility private
730
+ #
731
+ # There is an unnatural relationship between the :script and the
732
+ # :uia_strategy keys.
733
+ #
734
+ # @param [Hash] options The launch options passed to .run_with_options
735
+ # @param [RunLoop::Device] device The device under test.
736
+ # @param [RunLoop::Xcode] xcode The active Xcode.
737
+ #
738
+ # @return [Hash] with two keys: :script and :uia_strategy
739
+ def self.detect_instruments_script_and_strategy(options, device, xcode)
740
+ strategy = options[:uia_strategy]
741
+ script = options[:script]
742
+
743
+ if script
744
+ script = self.expect_instruments_script(script)
745
+ if !strategy
746
+ strategy = :host
747
+ end
748
+ else
749
+ if strategy
750
+ script = self.instruments_script_for_uia_strategy(strategy)
751
+ else
752
+ if options[:calabash_lite]
753
+ strategy = :host
754
+ script = self.instruments_script_for_uia_strategy(strategy)
755
+ else
756
+ strategy = self.detect_uia_strategy(options, device, xcode)
757
+ script = self.instruments_script_for_uia_strategy(strategy)
758
+ end
759
+ end
760
+ end
761
+
762
+ {
763
+ :script => script,
764
+ :strategy => strategy
765
+ }
766
+ end
767
+
768
+ # @!visibility private
769
+ #
770
+ # UIAutomation buffers log output in some very strange ways. RunLoop
771
+ # attempts to work around this buffering by forcing characters onto the
772
+ # UIALogger buffer. Once the buffer is full, UIAutomation will dump its
773
+ # contents. It is essential that the communication between UIAutomation
774
+ # and RunLoop be synchronized.
775
+ #
776
+ # Casual users should never set the :flush_uia_logs key; they should use the
777
+ # defaults.
778
+ #
779
+ # :no_flush is supported (for now) as alternative key.
780
+ #
781
+ # @param [Hash] options The launch options passed to .run_with_options
782
+ def self.detect_flush_uia_log_option(options)
783
+ if options.has_key?(:no_flush)
784
+ # Confusing.
785
+ # :no_flush == false means, flush the logs.
786
+ # :no_flush == true means, don't flush the logs.
787
+ return !options[:no_flush]
788
+ end
789
+
790
+ return options.fetch(:flush_uia_logs, true)
791
+ end
792
+
793
+ # @!visibility private
794
+ #
795
+ # @param [Hash] options The launch options passed to .run_with_options
796
+ def self.detect_reset_options(options)
797
+ return options[:reset] if options.has_key?(:reset)
798
+
799
+ return options[:reset_app_sandbox] if options.has_key?(:reset_app_sandbox)
800
+
801
+ RunLoop::Environment.reset_between_scenarios?
802
+ end
803
+
804
+ # Prepares the simulator for running.
805
+ #
806
+ # 1. enabling accessibility and software keyboard
807
+ # 2. installing / uninstalling apps
808
+ #
809
+ # TODO: move to CoreSimulator?
810
+ def self.prepare_simulator(app, device, xcode, simctl, reset_options)
811
+
812
+ # Validate the architecture.
813
+ self.expect_simulator_compatible_arch(device, app)
814
+
815
+ # Quits the simulator.
816
+ core_sim = RunLoop::CoreSimulator.new(device, app, :xcode => xcode)
817
+
818
+ # Calabash 0.x can only reset the app sandbox (true/false).
819
+ # Calabash 2.x has advanced reset options.
820
+ if reset_options
821
+ core_sim.reset_app_sandbox
822
+ end
823
+
824
+ # Will quit the simulator if it is running.
825
+ # @todo fix accessibility_enabled? so we don't have to quit the sim
826
+ # SimControl#accessibility_enabled? is always false during Core#prepare_simulator
827
+ # https://github.com/calabash/run_loop/issues/167
828
+ simctl.ensure_accessibility(device)
829
+
830
+ # Will quit the simulator if it is running.
831
+ # @todo fix software_keyboard_enabled? so we don't have to quit the sim
832
+ # SimControl#software_keyboard_enabled? is always false during Core#prepare_simulator
833
+ # https://github.com/calabash/run_loop/issues/168
834
+ simctl.ensure_software_keyboard(device)
835
+
836
+ # Launches the simulator if the app is not installed.
837
+ core_sim.install
838
+
839
+ # If CoreSimulator has already launched the simulator, it will not launch it again.
840
+ core_sim.launch_simulator
841
+ end
842
+
843
+ # @!visibility private
844
+ # Raise an error if the application binary is not compatible with the
845
+ # target simulator.
846
+ #
847
+ # @param [RunLoop::Device] device The device to install on.
848
+ # @param [RunLoop::App] app The app to install.
849
+ #
850
+ # @raise [RunLoop::IncompatibleArchitecture] Raises an error if the
851
+ # application binary is not compatible with the target simulator.
852
+ def self.expect_simulator_compatible_arch(device, app)
853
+ lipo = RunLoop::Lipo.new(app.path)
854
+ lipo.expect_compatible_arch(device)
855
+
856
+ RunLoop.log_debug("Simulator instruction set '#{device.instruction_set}' is compatible with '#{lipo.info}'")
857
+ end
858
+
859
+ # @!visibility private
860
+ def self.expect_instruments_script(script)
861
+ if script.is_a?(String)
862
+ unless File.exist?(script)
863
+ raise %Q[Expected instruments JavaScript file at path:
864
+
865
+ #{script}
866
+
867
+ Check the :script key in your launch options.]
868
+ end
869
+ script
870
+ elsif script.is_a?(Symbol)
871
+ path = self.script_for_key(script)
872
+ if !path
873
+ raise %Q[Expected :#{script} to be one of:
874
+
875
+ #{Core::SCRIPTS.keys.map { |key| ":#{key}" }.join("\n")}
876
+
877
+ Check the :script key in your launch options.]
878
+ end
879
+ path
880
+ else
881
+ raise %Q[Expected '#{script}' to be a Symbol or a String.
882
+
883
+ Check the :script key in your launch options.]
884
+ end
885
+ end
886
+
887
+ # @!visibility private
888
+ def self.instruments_script_for_uia_strategy(uia_strategy)
889
+ case uia_strategy
890
+ when :preferences
891
+ self.script_for_key(:run_loop_fast_uia)
892
+ when :host
893
+ self.script_for_key(:run_loop_host)
894
+ when :shared_element
895
+ self.script_for_key(:run_loop_shared_element)
896
+ else
897
+ self.script_for_key(:run_loop_basic)
898
+ end
899
+ end
900
+ end
901
+ end
902
+