run_loop_tcc 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|