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,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
+