run_loop_tcc 2.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/bin/run-loop +19 -0
- data/lib/run_loop/abstract.rb +18 -0
- data/lib/run_loop/app.rb +372 -0
- data/lib/run_loop/cache/cache.rb +68 -0
- data/lib/run_loop/cli/cli.rb +48 -0
- data/lib/run_loop/cli/codesign.rb +24 -0
- data/lib/run_loop/cli/errors.rb +11 -0
- data/lib/run_loop/cli/instruments.rb +160 -0
- data/lib/run_loop/cli/locale.rb +31 -0
- data/lib/run_loop/cli/simctl.rb +257 -0
- data/lib/run_loop/cli/tcc.rb +139 -0
- data/lib/run_loop/codesign.rb +76 -0
- data/lib/run_loop/core.rb +902 -0
- data/lib/run_loop/core_simulator.rb +960 -0
- data/lib/run_loop/detect_aut/detect.rb +185 -0
- data/lib/run_loop/detect_aut/errors.rb +126 -0
- data/lib/run_loop/detect_aut/xamarin_studio.rb +46 -0
- data/lib/run_loop/detect_aut/xcode.rb +157 -0
- data/lib/run_loop/device.rb +722 -0
- data/lib/run_loop/device_agent/app/CBX-Runner.app.zip +0 -0
- data/lib/run_loop/device_agent/bin/xctestctl +0 -0
- data/lib/run_loop/device_agent/cbxrunner.rb +156 -0
- data/lib/run_loop/device_agent/frameworks/Frameworks.zip +0 -0
- data/lib/run_loop/device_agent/frameworks.rb +65 -0
- data/lib/run_loop/device_agent/ipa/CBX-Runner.app.zip +0 -0
- data/lib/run_loop/device_agent/launcher.rb +51 -0
- data/lib/run_loop/device_agent/xcodebuild.rb +91 -0
- data/lib/run_loop/device_agent/xctestctl.rb +109 -0
- data/lib/run_loop/directory.rb +179 -0
- data/lib/run_loop/dnssd.rb +148 -0
- data/lib/run_loop/dot_dir.rb +87 -0
- data/lib/run_loop/dylib_injector.rb +145 -0
- data/lib/run_loop/encoding.rb +56 -0
- data/lib/run_loop/environment.rb +361 -0
- data/lib/run_loop/fifo.rb +40 -0
- data/lib/run_loop/host_cache.rb +128 -0
- data/lib/run_loop/http/error.rb +15 -0
- data/lib/run_loop/http/request.rb +44 -0
- data/lib/run_loop/http/retriable_client.rb +166 -0
- data/lib/run_loop/http/server.rb +17 -0
- data/lib/run_loop/instruments.rb +436 -0
- data/lib/run_loop/ipa.rb +142 -0
- data/lib/run_loop/l10n.rb +93 -0
- data/lib/run_loop/language.rb +63 -0
- data/lib/run_loop/lipo.rb +132 -0
- data/lib/run_loop/lldb.rb +52 -0
- data/lib/run_loop/locale.rb +101 -0
- data/lib/run_loop/logging.rb +111 -0
- data/lib/run_loop/otool.rb +76 -0
- data/lib/run_loop/patches/awesome_print.rb +17 -0
- data/lib/run_loop/physical_device/life_cycle.rb +268 -0
- data/lib/run_loop/plist_buddy.rb +189 -0
- data/lib/run_loop/process_terminator.rb +128 -0
- data/lib/run_loop/process_waiter.rb +117 -0
- data/lib/run_loop/regex.rb +19 -0
- data/lib/run_loop/shell.rb +103 -0
- data/lib/run_loop/sim_control.rb +1264 -0
- data/lib/run_loop/simctl.rb +275 -0
- data/lib/run_loop/sqlite.rb +61 -0
- data/lib/run_loop/strings.rb +88 -0
- data/lib/run_loop/tcc/TCC.db +0 -0
- data/lib/run_loop/tcc/tcc.rb +240 -0
- data/lib/run_loop/template.rb +61 -0
- data/lib/run_loop/version.rb +182 -0
- data/lib/run_loop/xcode.rb +318 -0
- data/lib/run_loop/xcrun.rb +107 -0
- data/lib/run_loop/xcuitest.rb +550 -0
- data/lib/run_loop.rb +230 -0
- data/plists/simctl/com.apple.UIAutomation.plist +0 -0
- data/plists/simctl/com.apple.UIAutomationPlugIn.plist +0 -0
- data/scripts/calabash_script_uia.js +28184 -0
- data/scripts/lib/json2.min.js +26 -0
- data/scripts/lib/log.js +26 -0
- data/scripts/lib/on_alert.js +224 -0
- data/scripts/read-cmd.sh +2 -0
- data/scripts/run_dismiss_location.js +89 -0
- data/scripts/run_loop_basic.js +34 -0
- data/scripts/run_loop_fast_uia.js +188 -0
- data/scripts/run_loop_host.js +117 -0
- data/scripts/run_loop_shared_element.js +125 -0
- data/scripts/timeout3 +23 -0
- data/scripts/udidetect +0 -0
- data/vendor-licenses/FBSimulatorControl.LICENSE +30 -0
- data/vendor-licenses/xctestctl.LICENSE +32 -0
- 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
|
+
|