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,722 @@
|
|
1
|
+
module RunLoop
|
2
|
+
class Device
|
3
|
+
|
4
|
+
require 'securerandom'
|
5
|
+
include RunLoop::Regex
|
6
|
+
|
7
|
+
# Starting in Xcode 7, iOS 9 simulators have a new "booting" state.
|
8
|
+
#
|
9
|
+
# The simulator must completely boot before run-loop tries to do things
|
10
|
+
# like installing an app or clearing an app sandbox. Run-loop tries to
|
11
|
+
# wait for a the simulator stabilize by watching the checksum of the
|
12
|
+
# simulator directory and the simulator log.
|
13
|
+
#
|
14
|
+
# On resource constrained devices or CI systems, the default settings may
|
15
|
+
# not work.
|
16
|
+
#
|
17
|
+
# You can override these values if they do not work in your environment.
|
18
|
+
#
|
19
|
+
# For cucumber users, the best place to override would be in your
|
20
|
+
# features/support/env.rb.
|
21
|
+
#
|
22
|
+
# For example:
|
23
|
+
#
|
24
|
+
# RunLoop::Device::SIM_STABLE_STATE_OPTIONS[:timeout] = 60
|
25
|
+
SIM_STABLE_STATE_OPTIONS = {
|
26
|
+
# The maximum amount of time to wait for the simulator
|
27
|
+
# to stabilize. No errors are raised if this timeout is
|
28
|
+
# exceeded - if the default 30 seconds has passed, the
|
29
|
+
# simulator is probably stable enough for subsequent
|
30
|
+
# operations.
|
31
|
+
:timeout => RunLoop::Environment.ci? ? 120 : 30
|
32
|
+
}
|
33
|
+
|
34
|
+
attr_reader :name
|
35
|
+
attr_reader :version
|
36
|
+
attr_reader :udid
|
37
|
+
attr_reader :state
|
38
|
+
attr_reader :simulator_root_dir
|
39
|
+
attr_reader :simulator_accessibility_plist_path
|
40
|
+
attr_reader :simulator_preferences_plist_path
|
41
|
+
attr_reader :simulator_log_file_path
|
42
|
+
attr_reader :pbuddy
|
43
|
+
|
44
|
+
# Create a new device.
|
45
|
+
#
|
46
|
+
# @param [String] name The name of the device. For sims this should be
|
47
|
+
# 'iPhone 5s' and for physical devices it will be the name the user gave
|
48
|
+
# to the device.
|
49
|
+
# @param [String, RunLoop::Version] version The iOS version that is running
|
50
|
+
# on the device. Can be a string or a Version instance.
|
51
|
+
# @param [String] udid The device identifier.
|
52
|
+
# @param [String] state (nil) This a simulator only value. It refers to
|
53
|
+
# the Booted/Shutdown/Creating state of the simulator. For pre-Xcode 6
|
54
|
+
# simulators, this value should be nil.
|
55
|
+
def initialize(name, version, udid, state=nil)
|
56
|
+
@name = name
|
57
|
+
@udid = udid
|
58
|
+
@state = state
|
59
|
+
|
60
|
+
if version.is_a? String
|
61
|
+
@version = RunLoop::Version.new version
|
62
|
+
else
|
63
|
+
@version = version
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns a device given a udid or name. In the case of a physical device,
|
68
|
+
# the udid is the device identifier. In the case of a simulator the name
|
69
|
+
# is the _instruments identifier_ as reported by
|
70
|
+
# `$ xcrun instruments -s devices` - this is the identifier that can be
|
71
|
+
# passed to instruments.
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# RunLoop::Device.device_with_identifier('iPhone 4s (8.3 Simulator'))
|
75
|
+
# RunLoop::Device.device_with_identifier('6E43E3CF-25F5-41CC-A833-588F043AE749')
|
76
|
+
# RunLoop::Device.device_with_identifier('denis') # Simulator or device named 'denis'
|
77
|
+
# RunLoop::Device.device_with_identifier('893688959205dc7eb48d603c558ede919ad8dd0c')
|
78
|
+
#
|
79
|
+
# Note that if you have a device and simulator with the same name, the
|
80
|
+
# simulator will always be selected.
|
81
|
+
#
|
82
|
+
# @param [String] udid_or_name A name or udid that identifies the device you
|
83
|
+
# are looking for.
|
84
|
+
# @param [Hash] options Allows callers to pass runtime models that might
|
85
|
+
# optimize performance (via memoization).
|
86
|
+
# @option options [RunLoop::Simctl] :simctl An instance of
|
87
|
+
# Simctl.
|
88
|
+
# @option options [RunLoop::Instruments] :instruments An instance of
|
89
|
+
# Instruments.
|
90
|
+
# @option options [RunLoop::Xcode] :xcode An instance of Xcode
|
91
|
+
#
|
92
|
+
# @return [RunLoop::Device] A device that matches `udid_or_name`.
|
93
|
+
# @raise [ArgumentError] If no matching device can be found.
|
94
|
+
def self.device_with_identifier(udid_or_name, options={})
|
95
|
+
if options.is_a?(RunLoop::SimControl)
|
96
|
+
raise ArgumentError, %q[Support for the 'sim_control' argument has been
|
97
|
+
removed (1.5.0). It has been replaced by an options hash with two keys:
|
98
|
+
:simctl and :instruments. Please update your sources.))]
|
99
|
+
end
|
100
|
+
|
101
|
+
default_options = {
|
102
|
+
:simctl => RunLoop::Simctl.new,
|
103
|
+
:instruments => RunLoop::Instruments.new,
|
104
|
+
:xcode => RunLoop::Xcode.new
|
105
|
+
}
|
106
|
+
|
107
|
+
merged_options = default_options.merge(options)
|
108
|
+
|
109
|
+
instruments = merged_options[:instruments]
|
110
|
+
simctl = merged_options[:simctl]
|
111
|
+
|
112
|
+
xcode = RunLoop::Xcode.new
|
113
|
+
simulator = simctl.simulators.detect do |sim|
|
114
|
+
sim.instruments_identifier(xcode) == udid_or_name ||
|
115
|
+
sim.udid == udid_or_name
|
116
|
+
end
|
117
|
+
|
118
|
+
return simulator if !simulator.nil?
|
119
|
+
|
120
|
+
physical_device = instruments.physical_devices.detect do |device|
|
121
|
+
device.name == udid_or_name ||
|
122
|
+
device.udid == udid_or_name
|
123
|
+
end
|
124
|
+
|
125
|
+
return physical_device if !physical_device.nil?
|
126
|
+
|
127
|
+
raise ArgumentError, "Could not find a device with a UDID or name matching '#{udid_or_name}'"
|
128
|
+
end
|
129
|
+
|
130
|
+
# @!visibility private
|
131
|
+
#
|
132
|
+
# Please don't call this method. It is for internal use only. The behavior
|
133
|
+
# may change at any time! You have been warned.
|
134
|
+
#
|
135
|
+
# @param [Hash] options The launch options passed to RunLoop::Core
|
136
|
+
# @param [RunLoop::Xcode] xcode An Xcode instance
|
137
|
+
# @param [RunLoop::Simctl] simctl A Simctl instance
|
138
|
+
# @param [RunLoop::Instruments] instruments An Instruments instance
|
139
|
+
#
|
140
|
+
# @raise [ArgumentError] If "device" is detected as the device target and
|
141
|
+
# there is no matching device.
|
142
|
+
# @raise [ArgumentError] If DEVICE_TARGET or options specify an identifier
|
143
|
+
# that does not match an iOS Simulator or physical device.
|
144
|
+
def self.detect_device(options, xcode, simctl, instruments)
|
145
|
+
device = self.device_from_opts_or_env(options)
|
146
|
+
|
147
|
+
# Passed an instance of RunLoop::Device
|
148
|
+
return device if device && device.is_a?(RunLoop::Device)
|
149
|
+
|
150
|
+
# Need to infer what what the user wants from the environment and options.
|
151
|
+
if device == "device"
|
152
|
+
identifier = self.detect_physical_device_on_usb
|
153
|
+
self.ensure_physical_device_connected(identifier, options)
|
154
|
+
elsif device.nil? || device == "" || device == "simulator"
|
155
|
+
identifier = RunLoop::Core.default_simulator(xcode)
|
156
|
+
else
|
157
|
+
identifier = device
|
158
|
+
end
|
159
|
+
|
160
|
+
# Raises ArgumentError if no matching device can be found.
|
161
|
+
self.device_with_identifier(identifier,
|
162
|
+
simctl: simctl,
|
163
|
+
instruments: instruments)
|
164
|
+
end
|
165
|
+
|
166
|
+
# @!visibility private
|
167
|
+
def to_s
|
168
|
+
if simulator?
|
169
|
+
"#<Simulator: #{name} (#{version.to_s}) #{udid} #{instruction_set}>"
|
170
|
+
else
|
171
|
+
"#<Device: #{name} (#{version.to_s}) #{udid}>"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# @!visibility private
|
176
|
+
def inspect
|
177
|
+
to_s
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns and instruments-ready device identifier that is a suitable value
|
181
|
+
# for DEVICE_TARGET environment variable.
|
182
|
+
#
|
183
|
+
# @param [RunLoop::Xcode] xcode The version of the active
|
184
|
+
# Xcode.
|
185
|
+
# @return [String] An instruments-ready device identifier.
|
186
|
+
# @raise [RuntimeError] If trying to obtain a instruments-ready identifier
|
187
|
+
# for a simulator when Xcode < 6.
|
188
|
+
def instruments_identifier(xcode)
|
189
|
+
if physical_device?
|
190
|
+
udid
|
191
|
+
else
|
192
|
+
if version == RunLoop::Version.new('7.0.3')
|
193
|
+
version_part = version.to_s
|
194
|
+
else
|
195
|
+
version_part = "#{version.major}.#{version.minor}"
|
196
|
+
end
|
197
|
+
|
198
|
+
if xcode.version_gte_7?
|
199
|
+
"#{name} (#{version_part})"
|
200
|
+
else
|
201
|
+
"#{name} (#{version_part} Simulator)"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Is this a physical device?
|
207
|
+
# @return [Boolean] Returns true if this is a device.
|
208
|
+
def physical_device?
|
209
|
+
if udid.nil?
|
210
|
+
stack = Kernel.caller(0, 6)[0..-1].join("\n")
|
211
|
+
raise RuntimeError,
|
212
|
+
%Q[udid is nil
|
213
|
+
|
214
|
+
#{stack}
|
215
|
+
|
216
|
+
name: #{name}
|
217
|
+
version: #{version}
|
218
|
+
]
|
219
|
+
end
|
220
|
+
!udid[DEVICE_UDID_REGEX, 0].nil?
|
221
|
+
end
|
222
|
+
|
223
|
+
# Is this a simulator?
|
224
|
+
# @return [Boolean] Returns true if this is a simulator.
|
225
|
+
def simulator?
|
226
|
+
!physical_device?
|
227
|
+
end
|
228
|
+
|
229
|
+
# Return the instruction set for this device.
|
230
|
+
#
|
231
|
+
# **Simulator**
|
232
|
+
# The simulator instruction set will be i386 or x86_64 depending on the
|
233
|
+
# the (marketing) name of the device.
|
234
|
+
#
|
235
|
+
# @note Finding the instruction set of a device requires a third-party tool
|
236
|
+
# like ideviceinfo. Example:
|
237
|
+
# `$ ideviceinfo -u 89b59 < snip > ab7ba --key 'CPUArchitecture' => arm64`
|
238
|
+
#
|
239
|
+
# @raise [RuntimeError] Raises an error if this device is a physical device.
|
240
|
+
# @return [String] An instruction set.
|
241
|
+
def instruction_set
|
242
|
+
if simulator?
|
243
|
+
if ['iPhone 4s', 'iPhone 5', 'iPad 2', 'iPad Retina'].include?(self.name)
|
244
|
+
'i386'
|
245
|
+
else
|
246
|
+
'x86_64'
|
247
|
+
end
|
248
|
+
else
|
249
|
+
raise 'Finding the instruction set of a device requires a third-party tool like ideviceinfo'
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# @!visibility private
|
254
|
+
# The device `state` is reported by the simctl tool.
|
255
|
+
#
|
256
|
+
# The expected values from simctl are:
|
257
|
+
#
|
258
|
+
# * Booted
|
259
|
+
# * Shutdown
|
260
|
+
# * Shutting Down
|
261
|
+
#
|
262
|
+
# To handle exceptional cases, there are these two additional states:
|
263
|
+
#
|
264
|
+
# * Unavailable # Should never occur
|
265
|
+
# * Unknown # A stub for future changes
|
266
|
+
def update_simulator_state
|
267
|
+
if physical_device?
|
268
|
+
raise RuntimeError, 'This method is available only for simulators'
|
269
|
+
end
|
270
|
+
|
271
|
+
@state = fetch_simulator_state
|
272
|
+
end
|
273
|
+
|
274
|
+
# @!visibility private
|
275
|
+
def simulator_root_dir
|
276
|
+
@simulator_root_dir ||= lambda {
|
277
|
+
return nil if physical_device?
|
278
|
+
File.join(CORE_SIMULATOR_DEVICE_DIR, udid)
|
279
|
+
}.call
|
280
|
+
end
|
281
|
+
|
282
|
+
# @!visibility private
|
283
|
+
def simulator_accessibility_plist_path
|
284
|
+
@simulator_accessibility_plist_path ||= lambda {
|
285
|
+
return nil if physical_device?
|
286
|
+
File.join(simulator_root_dir, 'data/Library/Preferences/com.apple.Accessibility.plist')
|
287
|
+
}.call
|
288
|
+
end
|
289
|
+
|
290
|
+
# @!visibility private
|
291
|
+
def simulator_preferences_plist_path
|
292
|
+
@simulator_preferences_plist_path ||= lambda {
|
293
|
+
return nil if physical_device?
|
294
|
+
File.join(simulator_root_dir, 'data/Library/Preferences/com.apple.Preferences.plist')
|
295
|
+
}.call
|
296
|
+
end
|
297
|
+
|
298
|
+
# @!visibility private
|
299
|
+
def simulator_log_file_path
|
300
|
+
@simulator_log_file_path ||= lambda {
|
301
|
+
return nil if physical_device?
|
302
|
+
File.join(CORE_SIMULATOR_LOGS_DIR, udid, 'system.log')
|
303
|
+
}.call
|
304
|
+
end
|
305
|
+
|
306
|
+
# @!visibility private
|
307
|
+
def simulator_device_plist
|
308
|
+
@simulator_device_plist ||= lambda do
|
309
|
+
return nil if physical_device?
|
310
|
+
File.join(simulator_root_dir, 'device.plist')
|
311
|
+
end.call
|
312
|
+
end
|
313
|
+
|
314
|
+
# @!visibility private
|
315
|
+
def simulator_global_preferences_path
|
316
|
+
@simulator_global_preferences_path ||= lambda do
|
317
|
+
return nil if physical_device?
|
318
|
+
File.join(simulator_root_dir, "data/Library/Preferences/.GlobalPreferences.plist")
|
319
|
+
end.call
|
320
|
+
end
|
321
|
+
|
322
|
+
# @!visibility private
|
323
|
+
def simulator_tcc_db
|
324
|
+
@simulator_tcc_db ||= lambda do
|
325
|
+
return nil if physical_device?
|
326
|
+
path = File.join(simulator_root_dir, "data/Library/TCC/TCC.db")
|
327
|
+
simulator_ensure_tcc_db(path)
|
328
|
+
end.call
|
329
|
+
end
|
330
|
+
|
331
|
+
# @!visibility private
|
332
|
+
#
|
333
|
+
# Waits for three conditions:
|
334
|
+
#
|
335
|
+
# 1. The SHA sum of the simulator data/ directory to be stable.
|
336
|
+
# 2. No more log messages are begin generated.
|
337
|
+
# 3. 1 and 2 must hold for 1.5 seconds.
|
338
|
+
#
|
339
|
+
# When the simulator version is >= iOS 9, two more conditions are added to
|
340
|
+
# get past the iOS 9+ boot screen.
|
341
|
+
#
|
342
|
+
# 4. Wait for com.apple.audio.SystemSoundServer-iOS-Simulator process to
|
343
|
+
# start.
|
344
|
+
# 5. 1 and 2 must hold for 1.5 seconds.
|
345
|
+
#
|
346
|
+
# When the simulator version is >= iOS 9 and the device is an iPad another
|
347
|
+
# condition is added because simctl fails to correctly install applications;
|
348
|
+
# the app and data container exists, but Springboard does not detect them.
|
349
|
+
#
|
350
|
+
# 6. 1 and 2 must hold for 1.5 seconds.
|
351
|
+
def simulator_wait_for_stable_state
|
352
|
+
|
353
|
+
# How long to wait between stability checks.
|
354
|
+
# Shorter than this gives false positives.
|
355
|
+
delay = 0.5
|
356
|
+
|
357
|
+
# How many times to wait for stable state.
|
358
|
+
max_stable_count = 3
|
359
|
+
|
360
|
+
# How long to wait for iOS 9 boot screen.
|
361
|
+
boot_screen_wait_options = {
|
362
|
+
:max_boot_screen_wait => 10,
|
363
|
+
:raise_on_timeout => false
|
364
|
+
}
|
365
|
+
|
366
|
+
# How much additional time to wait for iOS 9+ iPads.
|
367
|
+
#
|
368
|
+
# Installing and launching on iPads is problematic.
|
369
|
+
# Sometimes the app is installed, but SpringBoard does
|
370
|
+
# not recognize that the app is installed even though
|
371
|
+
# simctl says that it is.
|
372
|
+
additional_ipad_delay = delay * 2
|
373
|
+
|
374
|
+
# Adjust for CI environments
|
375
|
+
if RunLoop::Environment.ci?
|
376
|
+
max_stable_count = 5
|
377
|
+
boot_screen_wait_options[:max_boot_screen_wait] = 20
|
378
|
+
additional_ipad_delay = delay * 4
|
379
|
+
end
|
380
|
+
|
381
|
+
# iOS 9 simulators have an additional boot screen.
|
382
|
+
is_gte_ios9 = version >= RunLoop::Version.new('9.0')
|
383
|
+
|
384
|
+
# iOS 9 iPad simulators need additional time to stabilize.
|
385
|
+
is_ipad = simulator_is_ipad?
|
386
|
+
|
387
|
+
timeout = SIM_STABLE_STATE_OPTIONS[:timeout]
|
388
|
+
now = Time.now
|
389
|
+
poll_until = now + timeout
|
390
|
+
|
391
|
+
RunLoop.log_debug("Waiting for simulator to stabilize with timeout: #{timeout} seconds")
|
392
|
+
|
393
|
+
current_dir_sha = simulator_data_directory_sha
|
394
|
+
current_log_sha = simulator_log_file_sha
|
395
|
+
is_stable = false
|
396
|
+
waited_for_boot = false
|
397
|
+
waited_for_ipad = false
|
398
|
+
stable_count = 0
|
399
|
+
|
400
|
+
while Time.now < poll_until do
|
401
|
+
latest_dir_sha = simulator_data_directory_sha
|
402
|
+
latest_log_sha = simulator_log_file_sha
|
403
|
+
|
404
|
+
is_stable = [current_dir_sha == latest_dir_sha,
|
405
|
+
current_log_sha == latest_log_sha].all?
|
406
|
+
|
407
|
+
if is_stable
|
408
|
+
stable_count = stable_count + 1
|
409
|
+
if stable_count == max_stable_count
|
410
|
+
if is_gte_ios9 && !waited_for_boot
|
411
|
+
process_name = "com.apple.audio.SystemSoundServer-iOS-Simulator"
|
412
|
+
RunLoop::ProcessWaiter.new(process_name, boot_screen_wait_options).wait_for_any
|
413
|
+
waited_for_boot = true
|
414
|
+
stable_count = 0
|
415
|
+
elsif is_gte_ios9 && is_ipad && !waited_for_ipad
|
416
|
+
RunLoop.log_debug("Waiting additional time for iOS 9 iPad to stabilize")
|
417
|
+
sleep(additional_ipad_delay)
|
418
|
+
waited_for_ipad = true
|
419
|
+
stable_count = 0
|
420
|
+
else
|
421
|
+
break
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
current_dir_sha = latest_dir_sha
|
427
|
+
current_log_sha = latest_log_sha
|
428
|
+
sleep(delay)
|
429
|
+
end
|
430
|
+
|
431
|
+
if is_stable
|
432
|
+
elapsed = Time.now - now
|
433
|
+
RunLoop.log_debug("Waited a total of #{elapsed} seconds for simulator to stabilize")
|
434
|
+
else
|
435
|
+
RunLoop.log_debug("Timed out after #{timeout} seconds waiting for simulator to stabilize")
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
# @!visibility private
|
440
|
+
#
|
441
|
+
# Sets the AppleLocale key in the .GlobalPreferences.plist file
|
442
|
+
#
|
443
|
+
# @param [String] locale_code a locale code
|
444
|
+
#
|
445
|
+
# @return [RunLoop::Locale] a locale instance
|
446
|
+
#
|
447
|
+
# @raise [RuntimeError] if this is a physical device
|
448
|
+
# @raise [ArgumentError] if the locale code is invalid
|
449
|
+
def simulator_set_locale(locale_code)
|
450
|
+
if physical_device?
|
451
|
+
raise RuntimeError, "This method is for Simulators only"
|
452
|
+
end
|
453
|
+
|
454
|
+
locale = RunLoop::Locale.locale_for_code(locale_code, self)
|
455
|
+
|
456
|
+
global_plist = simulator_global_preferences_path
|
457
|
+
pbuddy.plist_set("AppleLocale", "string", locale.code, global_plist)
|
458
|
+
|
459
|
+
locale
|
460
|
+
end
|
461
|
+
|
462
|
+
# @!visibility private
|
463
|
+
#
|
464
|
+
# Returns the AppleLanguages array in global plist as an array
|
465
|
+
#
|
466
|
+
# @return [Array<String>] list of language codes
|
467
|
+
def simulator_languages
|
468
|
+
global_plist = simulator_global_preferences_path
|
469
|
+
out = pbuddy.plist_read("AppleLanguages", global_plist)
|
470
|
+
|
471
|
+
# example: "Array {\n en\n en-US\n}"
|
472
|
+
# I am intentionally punting on this because I don't want
|
473
|
+
# to track down edge cases until the output of this method
|
474
|
+
# is actually used.
|
475
|
+
result = [out]
|
476
|
+
begin
|
477
|
+
result = out.strip.gsub(/[\{\}]/, "").split($-0).map do |elm|
|
478
|
+
elm.strip
|
479
|
+
end[1..-1]
|
480
|
+
rescue => e
|
481
|
+
RunLoop.log_debug("Caught error #{e.message} trying to parse '#{out}'")
|
482
|
+
end
|
483
|
+
|
484
|
+
result
|
485
|
+
end
|
486
|
+
|
487
|
+
# @!visibility private
|
488
|
+
#
|
489
|
+
# Sets the first element in the AppleLanguages array to lang_code.
|
490
|
+
#
|
491
|
+
# @param [String] lang_code a language code
|
492
|
+
#
|
493
|
+
# @return [Array<String>] list of language codes
|
494
|
+
#
|
495
|
+
# @raise [RuntimeError] if this is a physical device
|
496
|
+
# @raise [ArgumentError] if the language code is invalid
|
497
|
+
def simulator_set_language(lang_code)
|
498
|
+
if physical_device?
|
499
|
+
raise RuntimeError, "This method is for Simulators only"
|
500
|
+
end
|
501
|
+
|
502
|
+
if !RunLoop::Language.valid_code_for_device?(lang_code, self)
|
503
|
+
raise ArgumentError,
|
504
|
+
"The language code '#{lang_code}' is not valid for this device"
|
505
|
+
end
|
506
|
+
|
507
|
+
global_plist = simulator_global_preferences_path
|
508
|
+
|
509
|
+
cmd = [
|
510
|
+
"PlistBuddy",
|
511
|
+
"-c",
|
512
|
+
"Add :AppleLanguages:0 string '#{lang_code}'",
|
513
|
+
global_plist
|
514
|
+
]
|
515
|
+
|
516
|
+
# RunLoop::PlistBuddy cannot add items to arrays.
|
517
|
+
xcrun.run_command_in_context(cmd, {:log_cmd => true})
|
518
|
+
|
519
|
+
simulator_languages
|
520
|
+
end
|
521
|
+
|
522
|
+
private
|
523
|
+
|
524
|
+
# @!visibility private
|
525
|
+
def xcrun
|
526
|
+
RunLoop::Xcrun.new
|
527
|
+
end
|
528
|
+
|
529
|
+
# @!visibility private
|
530
|
+
def pbuddy
|
531
|
+
RunLoop::PlistBuddy.new
|
532
|
+
end
|
533
|
+
|
534
|
+
# @!visibility private
|
535
|
+
def detect_state_from_line(line)
|
536
|
+
|
537
|
+
if line[/unavailable/, 0]
|
538
|
+
RunLoop.log_debug("Simulator state is unavailable: #{line}")
|
539
|
+
return 'Unavailable'
|
540
|
+
end
|
541
|
+
|
542
|
+
state = line[/(Booted|Shutdown|Shutting Down)/,0]
|
543
|
+
|
544
|
+
if state.nil?
|
545
|
+
RunLoop.log_debug("Simulator state is unknown: #{line}")
|
546
|
+
'Unknown'
|
547
|
+
else
|
548
|
+
state
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
# @!visibility private
|
553
|
+
def fetch_simulator_state
|
554
|
+
if physical_device?
|
555
|
+
raise RuntimeError, 'This method is available only for simulators'
|
556
|
+
end
|
557
|
+
|
558
|
+
args = ['simctl', 'list', 'devices']
|
559
|
+
hash = xcrun.run_command_in_context(args)
|
560
|
+
out = hash[:out]
|
561
|
+
|
562
|
+
matched_line = out.split("\n").find do |line|
|
563
|
+
line.include?(udid)
|
564
|
+
end
|
565
|
+
|
566
|
+
if matched_line.nil?
|
567
|
+
raise RuntimeError,
|
568
|
+
"Expected a simulator with udid '#{udid}', but found none"
|
569
|
+
end
|
570
|
+
|
571
|
+
detect_state_from_line(matched_line)
|
572
|
+
end
|
573
|
+
|
574
|
+
# @!visibility private
|
575
|
+
CORE_SIMULATOR_DEVICE_DIR = File.join(RunLoop::Environment.user_home_directory,
|
576
|
+
"Library",
|
577
|
+
"Developer",
|
578
|
+
"CoreSimulator",
|
579
|
+
"Devices")
|
580
|
+
|
581
|
+
# @!visibility private
|
582
|
+
CORE_SIMULATOR_LOGS_DIR = File.join(RunLoop::Environment.user_home_directory,
|
583
|
+
"Library",
|
584
|
+
"Logs",
|
585
|
+
"CoreSimulator")
|
586
|
+
|
587
|
+
# @!visibility private
|
588
|
+
def self.device_from_options(options)
|
589
|
+
options[:device] || options[:device_target] || options[:udid]
|
590
|
+
end
|
591
|
+
|
592
|
+
# @!visibility private
|
593
|
+
def self.device_from_environment
|
594
|
+
RunLoop::Environment.device_target
|
595
|
+
end
|
596
|
+
|
597
|
+
# @!visibility private
|
598
|
+
def self.device_from_opts_or_env(options)
|
599
|
+
self.device_from_options(options) || self.device_from_environment
|
600
|
+
end
|
601
|
+
|
602
|
+
# @!visibility private
|
603
|
+
UDID_DETECT = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", 'scripts', "udidetect"))
|
604
|
+
|
605
|
+
# @!visibility private
|
606
|
+
def self.detect_physical_device_on_usb
|
607
|
+
require "command_runner"
|
608
|
+
|
609
|
+
udid = nil
|
610
|
+
begin
|
611
|
+
hash = CommandRunner.run([UDID_DETECT], timeout: 1)
|
612
|
+
udid = hash[:out].chomp
|
613
|
+
if udid == ""
|
614
|
+
udid = nil
|
615
|
+
end
|
616
|
+
rescue => e
|
617
|
+
RunLoop.log_debug("Running `udidetect` raised: #{e}")
|
618
|
+
ensure
|
619
|
+
`killall udidetect &> /dev/null`
|
620
|
+
end
|
621
|
+
udid
|
622
|
+
end
|
623
|
+
|
624
|
+
# @!visibility private
|
625
|
+
def simulator_data_directory_sha
|
626
|
+
path = File.join(simulator_root_dir, 'data')
|
627
|
+
begin
|
628
|
+
# Typically, this returns in < 0.3 seconds.
|
629
|
+
Timeout.timeout(10, TimeoutError) do
|
630
|
+
# Errors are ignorable and users are confused by the messages.
|
631
|
+
options = { :handle_errors_by => :ignoring }
|
632
|
+
RunLoop::Directory.directory_digest(path, options)
|
633
|
+
end
|
634
|
+
rescue => _
|
635
|
+
SecureRandom.uuid
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
def simulator_install_tcc_db(path)
|
640
|
+
dir = File.expand_path(File.dirname(__FILE__))
|
641
|
+
source = File.join(dir, "tcc", "TCC.db")
|
642
|
+
|
643
|
+
FileUtils.mkdir_p(File.expand_path(File.dirname(path)))
|
644
|
+
FileUtils.cp(source, path)
|
645
|
+
path
|
646
|
+
end
|
647
|
+
|
648
|
+
# @!visibility private
|
649
|
+
def simulator_ensure_tcc_db(path)
|
650
|
+
if File.exist?(path)
|
651
|
+
path
|
652
|
+
else
|
653
|
+
simulator_install_tcc_db(path)
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
# @!visibility private
|
658
|
+
def simulator_log_file_sha
|
659
|
+
file = simulator_log_file_path
|
660
|
+
|
661
|
+
return nil if !File.exist?(file)
|
662
|
+
|
663
|
+
sha = OpenSSL::Digest::SHA256.new
|
664
|
+
|
665
|
+
begin
|
666
|
+
sha << File.read(file)
|
667
|
+
rescue => _
|
668
|
+
sha = SecureRandom.uuid
|
669
|
+
end
|
670
|
+
|
671
|
+
sha
|
672
|
+
end
|
673
|
+
|
674
|
+
# @!visibility private
|
675
|
+
# Value of <UDID>/.device.plist 'deviceType' key.
|
676
|
+
def simulator_device_type
|
677
|
+
plist = File.join(simulator_device_plist)
|
678
|
+
pbuddy.plist_read("deviceType", plist)
|
679
|
+
end
|
680
|
+
|
681
|
+
# @!visibility private
|
682
|
+
def simulator_is_ipad?
|
683
|
+
simulator_device_type[/iPad/, 0]
|
684
|
+
end
|
685
|
+
|
686
|
+
# @!visibility private
|
687
|
+
def self.ensure_physical_device_connected(identifier, options)
|
688
|
+
if identifier.nil?
|
689
|
+
env = self.device_from_environment
|
690
|
+
if env == "device"
|
691
|
+
message = "DEVICE_TARGET=device means that you want to test on physical device"
|
692
|
+
elsif env && env[DEVICE_UDID_REGEX, 0]
|
693
|
+
message = "DEVICE_TARGET=#{env} did not match any connected device"
|
694
|
+
else
|
695
|
+
if options[:device]
|
696
|
+
key = ":device"
|
697
|
+
elsif options[:device_target]
|
698
|
+
key = ":device_target"
|
699
|
+
else
|
700
|
+
key = ":udid"
|
701
|
+
end
|
702
|
+
message = "#{key} => \"device\" means that you want to test on a physical device"
|
703
|
+
end
|
704
|
+
|
705
|
+
raise ArgumentError, %Q[Expected a physical device to be connected via USB.
|
706
|
+
|
707
|
+
#{message}
|
708
|
+
|
709
|
+
1. Is your device connected?
|
710
|
+
2. Does your device appear in the output of `xcrun instruments -s devices`?
|
711
|
+
3. Does your device appear in Xcode > Windows > Devices without a warning message?
|
712
|
+
|
713
|
+
Please see the documentation about testing on physical devices.
|
714
|
+
|
715
|
+
https://github.com/calabash/calabash-ios/wiki/Testing-on-Physical-Devices
|
716
|
+
]
|
717
|
+
end
|
718
|
+
true
|
719
|
+
end
|
720
|
+
end
|
721
|
+
end
|
722
|
+
|