run_loop 1.5.5 → 1.5.6.pre1
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 +4 -4
- data/lib/run_loop/cli/simctl.rb +37 -82
- data/lib/run_loop/core.rb +20 -17
- data/lib/run_loop/core_simulator.rb +648 -0
- data/lib/run_loop/device.rb +8 -8
- data/lib/run_loop/directory.rb +104 -17
- data/lib/run_loop/dot_dir.rb +83 -0
- data/lib/run_loop/environment.rb +27 -8
- data/lib/run_loop/instruments.rb +89 -1
- data/lib/run_loop/logging.rb +11 -0
- data/lib/run_loop/process_terminator.rb +5 -17
- data/lib/run_loop/sim_control.rb +114 -77
- data/lib/run_loop/version.rb +15 -3
- data/lib/run_loop.rb +9 -10
- metadata +6 -7
- data/lib/run_loop/life_cycle/core_simulator.rb +0 -515
- data/lib/run_loop/life_cycle/simulator.rb +0 -73
- data/lib/run_loop/simctl/bridge.rb +0 -499
@@ -0,0 +1,648 @@
|
|
1
|
+
# A class to manage interactions with CoreSimulators.
|
2
|
+
class RunLoop::CoreSimulator
|
3
|
+
|
4
|
+
# @!visibility private
|
5
|
+
attr_reader :app
|
6
|
+
|
7
|
+
# @!visibility private
|
8
|
+
attr_reader :device
|
9
|
+
|
10
|
+
# @!visibility private
|
11
|
+
attr_reader :pbuddy
|
12
|
+
|
13
|
+
# @!visibility private
|
14
|
+
attr_reader :xcode
|
15
|
+
|
16
|
+
# @!visibility private
|
17
|
+
attr_reader :xcrun
|
18
|
+
|
19
|
+
# @!visibility private
|
20
|
+
attr_reader :simulator_pid
|
21
|
+
|
22
|
+
# @!visibility private
|
23
|
+
METADATA_PLIST = '.com.apple.mobile_container_manager.metadata.plist'
|
24
|
+
|
25
|
+
# @!visibility private
|
26
|
+
CORE_SIMULATOR_DEVICE_DIR = File.expand_path('~/Library/Developer/CoreSimulator/Devices')
|
27
|
+
|
28
|
+
# @!visibility private
|
29
|
+
WAIT_FOR_DEVICE_STATE_OPTS = {
|
30
|
+
interval: 0.1,
|
31
|
+
timeout: 5
|
32
|
+
}
|
33
|
+
|
34
|
+
# @!visibility private
|
35
|
+
MANAGED_PROCESSES =
|
36
|
+
[
|
37
|
+
# This process is a daemon, and requires 'KILL' to terminate.
|
38
|
+
# Killing the process is fast, but it takes a long time to
|
39
|
+
# restart.
|
40
|
+
# ['com.apple.CoreSimulator.CoreSimulatorService', false],
|
41
|
+
|
42
|
+
# Probably do not need to quit this, but it is tempting to do so.
|
43
|
+
#['com.apple.CoreSimulator.SimVerificationService', false],
|
44
|
+
|
45
|
+
'SimulatorBridge',
|
46
|
+
'configd_sim',
|
47
|
+
|
48
|
+
# Does not always appear.
|
49
|
+
'CoreSimulatorBridge',
|
50
|
+
|
51
|
+
# Xcode 7
|
52
|
+
'ids_simd'
|
53
|
+
]
|
54
|
+
|
55
|
+
# @!visibility private
|
56
|
+
# Pattern:
|
57
|
+
# [ '< process name >', < send term first > ]
|
58
|
+
SIMULATOR_QUIT_PROCESSES =
|
59
|
+
[
|
60
|
+
# Xcode 7 start throwing this error.
|
61
|
+
['splashboardd', false],
|
62
|
+
|
63
|
+
# Xcode < 5.1
|
64
|
+
['iPhone Simulator.app', true],
|
65
|
+
|
66
|
+
# 7.0 < Xcode <= 6.0
|
67
|
+
['iOS Simulator.app', true],
|
68
|
+
|
69
|
+
# Xcode >= 7.0
|
70
|
+
['Simulator.app', true],
|
71
|
+
|
72
|
+
# Multiple launchd_sim processes have been causing problems. This
|
73
|
+
# is a first pass at investigating what it would mean to kill the
|
74
|
+
# launchd_sim process.
|
75
|
+
['launchd_sim', false],
|
76
|
+
|
77
|
+
# assetsd instances clobber each other and are not properly
|
78
|
+
# killed when quiting the simulator.
|
79
|
+
['assetsd', false],
|
80
|
+
|
81
|
+
# iproxy is started by UITest.
|
82
|
+
['iproxy', false],
|
83
|
+
|
84
|
+
# Started by Xamarin Studio, this is the parent process of the
|
85
|
+
# processes launched by Xamarin's interaction with
|
86
|
+
# CoreSimulatorBridge.
|
87
|
+
['csproxy', false],
|
88
|
+
]
|
89
|
+
|
90
|
+
# @!visibility private
|
91
|
+
#
|
92
|
+
# Terminate CoreSimulator related processes. This processes can accumulate
|
93
|
+
# as testing proceeds and can cause instability.
|
94
|
+
def self.terminate_core_simulator_processes
|
95
|
+
|
96
|
+
self.quit_simulator
|
97
|
+
|
98
|
+
MANAGED_PROCESSES.each do |process_name|
|
99
|
+
send_term_first = false
|
100
|
+
self.term_or_kill(process_name, send_term_first)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# @!visibility private
|
105
|
+
# Quit any Simulator.app or iOS Simulator.app
|
106
|
+
def self.quit_simulator
|
107
|
+
SIMULATOR_QUIT_PROCESSES.each do |process_details|
|
108
|
+
process_name = process_details[0]
|
109
|
+
send_term_first = process_details[1]
|
110
|
+
self.term_or_kill(process_name, send_term_first)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# @param [RunLoop::Device] device The device.
|
115
|
+
# @param [RunLoop::App] app The application.
|
116
|
+
# @param [Hash] options Controls the behavior of this class.
|
117
|
+
# @option options :quit_sim_on_init (true) If true, quit any running
|
118
|
+
# @option options :xcode An instance of Xcode to use
|
119
|
+
# simulators in the initialize method.
|
120
|
+
def initialize(device, app, options={})
|
121
|
+
defaults = { :quit_sim_on_init => true }
|
122
|
+
merged = defaults.merge(options)
|
123
|
+
|
124
|
+
@app = app
|
125
|
+
@device = device
|
126
|
+
|
127
|
+
@xcode = merged[:xcode]
|
128
|
+
|
129
|
+
if merged[:quit_sim_on_init]
|
130
|
+
RunLoop::CoreSimulator.quit_simulator
|
131
|
+
end
|
132
|
+
|
133
|
+
# stdio.pipe - can cause problems finding the SHA or size data dir
|
134
|
+
rm_instruments_pipe
|
135
|
+
end
|
136
|
+
|
137
|
+
# @!visibility private
|
138
|
+
def pbuddy
|
139
|
+
@pbuddy ||= RunLoop::PlistBuddy.new
|
140
|
+
end
|
141
|
+
|
142
|
+
# @!visibility private
|
143
|
+
def xcode
|
144
|
+
@xcode ||= RunLoop::Xcode.new
|
145
|
+
end
|
146
|
+
|
147
|
+
# @!visibility private
|
148
|
+
def xcrun
|
149
|
+
@xcrun ||= RunLoop::Xcrun.new
|
150
|
+
end
|
151
|
+
|
152
|
+
# @!visibility private
|
153
|
+
def simulator_pid
|
154
|
+
@simulator_pid
|
155
|
+
end
|
156
|
+
|
157
|
+
# Launch the simulator indicated by device.
|
158
|
+
def launch_simulator
|
159
|
+
|
160
|
+
if sim_pid != nil
|
161
|
+
# There is a running simulator.
|
162
|
+
|
163
|
+
# Did we launch it?
|
164
|
+
if sim_pid == simulator_pid
|
165
|
+
# Nothing to do, we already launched the simulator.
|
166
|
+
return
|
167
|
+
else
|
168
|
+
# We did not launch this simulator; quit it.
|
169
|
+
RunLoop::CoreSimulator.quit_simulator
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
args = ['open', '-g', '-a', sim_app_path, '--args', '-CurrentDeviceUDID', device.udid]
|
174
|
+
|
175
|
+
RunLoop.log_debug("Launching #{device} with:")
|
176
|
+
RunLoop.log_unix_cmd("xcrun #{args.join(' ')}")
|
177
|
+
|
178
|
+
start_time = Time.now
|
179
|
+
|
180
|
+
pid = spawn('xcrun', *args)
|
181
|
+
Process.detach(pid)
|
182
|
+
|
183
|
+
# Keep track of the pid so we can know if we have already launched this sim.
|
184
|
+
@simulator_pid = pid
|
185
|
+
|
186
|
+
options = { :timeout => 5, :raise_on_timeout => true }
|
187
|
+
RunLoop::ProcessWaiter.new(sim_name, options).wait_for_any
|
188
|
+
|
189
|
+
device.simulator_wait_for_stable_state
|
190
|
+
|
191
|
+
elapsed = Time.now - start_time
|
192
|
+
RunLoop.log_debug("Took #{elapsed} seconds to launch the simulator")
|
193
|
+
|
194
|
+
true
|
195
|
+
end
|
196
|
+
|
197
|
+
# Launch the app on the simulator.
|
198
|
+
#
|
199
|
+
# 1. If the app is not installed, it is installed.
|
200
|
+
# 2. If the app is different from the app that is installed, it is installed.
|
201
|
+
def launch
|
202
|
+
install
|
203
|
+
|
204
|
+
args = ['simctl', 'launch', device.udid, app.bundle_identifier]
|
205
|
+
hash = xcrun.exec(args, log_cmd: true, timeout: 20)
|
206
|
+
|
207
|
+
exit_status = hash[:exit_status]
|
208
|
+
|
209
|
+
if exit_status != 0
|
210
|
+
err = hash[:err]
|
211
|
+
RunLoop.log_error(err)
|
212
|
+
raise RuntimeError, "Could not launch #{app.bundle_identifier} on #{device}"
|
213
|
+
end
|
214
|
+
|
215
|
+
options = {
|
216
|
+
:timeout => 10,
|
217
|
+
:raise_on_timeout => true
|
218
|
+
}
|
219
|
+
|
220
|
+
RunLoop::ProcessWaiter.new(app.executable_name, options).wait_for_any
|
221
|
+
|
222
|
+
device.simulator_wait_for_stable_state
|
223
|
+
true
|
224
|
+
end
|
225
|
+
|
226
|
+
# Install the app.
|
227
|
+
#
|
228
|
+
# 1. If the app is not installed, it is installed.
|
229
|
+
# 2. Does nothing, if the app is the same as the one that is installed.
|
230
|
+
# 3. Installs the app if it is different from the installed app.
|
231
|
+
#
|
232
|
+
# The app sandbox is not touched.
|
233
|
+
def install
|
234
|
+
installed_app_bundle = installed_app_bundle_dir
|
235
|
+
|
236
|
+
# App is not installed. Use simctl interface to install.
|
237
|
+
if !installed_app_bundle
|
238
|
+
installed_app_bundle = install_app_with_simctl
|
239
|
+
else
|
240
|
+
ensure_app_same
|
241
|
+
end
|
242
|
+
|
243
|
+
installed_app_bundle
|
244
|
+
end
|
245
|
+
|
246
|
+
# Is this app installed?
|
247
|
+
def app_is_installed?
|
248
|
+
!installed_app_bundle_dir.nil?
|
249
|
+
end
|
250
|
+
|
251
|
+
# Resets the app sandbox.
|
252
|
+
#
|
253
|
+
# Does nothing if the app is not installed.
|
254
|
+
def reset_app_sandbox
|
255
|
+
return true if !app_is_installed?
|
256
|
+
|
257
|
+
wait_for_device_state('Shutdown')
|
258
|
+
|
259
|
+
reset_app_sandbox_internal
|
260
|
+
end
|
261
|
+
|
262
|
+
# Uninstalls the app and clears the sandbox.
|
263
|
+
def uninstall_app_and_sandbox
|
264
|
+
return true if !app_is_installed?
|
265
|
+
|
266
|
+
launch_simulator
|
267
|
+
|
268
|
+
args = ['simctl', 'uninstall', device.udid, app.bundle_identifier]
|
269
|
+
xcrun.exec(args, log_cmd: true, timeout: 20)
|
270
|
+
|
271
|
+
device.simulator_wait_for_stable_state
|
272
|
+
true
|
273
|
+
end
|
274
|
+
|
275
|
+
private
|
276
|
+
|
277
|
+
# @!visibility private
|
278
|
+
#
|
279
|
+
# This stdio.pipe file causes problems when checking the size and taking the
|
280
|
+
# checksum of the core simulator directory.
|
281
|
+
def rm_instruments_pipe
|
282
|
+
device_tmp_dir = File.join(device_data_dir, 'tmp')
|
283
|
+
Dir.glob("#{device_tmp_dir}/instruments_*/stdio.pipe") do |file|
|
284
|
+
if File.exist?(file)
|
285
|
+
RunLoop.log_debug("Deleting #{file}")
|
286
|
+
FileUtils.rm_rf(file)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# Send 'TERM' then 'KILL' to allow processes to quit cleanly.
|
292
|
+
def self.term_or_kill(process_name, send_term_first)
|
293
|
+
term_options = { :timeout => 0.5 }
|
294
|
+
kill_options = { :timeout => 0.5 }
|
295
|
+
|
296
|
+
RunLoop::ProcessWaiter.new(process_name).pids.each do |pid|
|
297
|
+
killed = false
|
298
|
+
|
299
|
+
if send_term_first
|
300
|
+
term = RunLoop::ProcessTerminator.new(pid, 'TERM', process_name, term_options)
|
301
|
+
killed = term.kill_process
|
302
|
+
end
|
303
|
+
|
304
|
+
unless killed
|
305
|
+
RunLoop::ProcessTerminator.new(pid, 'KILL', process_name, kill_options)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
# Returns the current simulator name.
|
310
|
+
#
|
311
|
+
# @return [String] A String suitable for searching for a pid, quitting, or
|
312
|
+
# launching the current simulator.
|
313
|
+
def sim_name
|
314
|
+
@sim_name ||= lambda {
|
315
|
+
if xcode.version_gte_7?
|
316
|
+
'Simulator'
|
317
|
+
elsif xcode.version_gte_6?
|
318
|
+
'iOS Simulator'
|
319
|
+
else
|
320
|
+
'iPhone Simulator'
|
321
|
+
end
|
322
|
+
}.call
|
323
|
+
end
|
324
|
+
|
325
|
+
# @!visibility private
|
326
|
+
# Returns the path to the current simulator.
|
327
|
+
#
|
328
|
+
# @return [String] The path to the simulator app for the current version of
|
329
|
+
# Xcode.
|
330
|
+
def sim_app_path
|
331
|
+
@sim_app_path ||= lambda {
|
332
|
+
dev_dir = xcode.developer_dir
|
333
|
+
if xcode.version_gte_7?
|
334
|
+
"#{dev_dir}/Applications/Simulator.app"
|
335
|
+
elsif xcode.version_gte_6?
|
336
|
+
"#{dev_dir}/Applications/iOS Simulator.app"
|
337
|
+
else
|
338
|
+
"#{dev_dir}/Platforms/iPhoneSimulator.platform/Developer/Applications/iPhone Simulator.app"
|
339
|
+
end
|
340
|
+
}.call
|
341
|
+
end
|
342
|
+
|
343
|
+
# @!visibility private
|
344
|
+
# Returns the current Simulator pid.
|
345
|
+
#
|
346
|
+
# @note Will only search for the current Xcode simulator.
|
347
|
+
#
|
348
|
+
# @return [String, nil] The pid as a String or nil if no process is found.
|
349
|
+
#
|
350
|
+
# @todo Convert this to force UTF8
|
351
|
+
def sim_pid
|
352
|
+
process_name = "MacOS/#{sim_name}"
|
353
|
+
`xcrun ps x -o pid,command | grep "#{process_name}" | grep -v grep`.strip.split(' ').first
|
354
|
+
end
|
355
|
+
|
356
|
+
# @!visibility private
|
357
|
+
def install_app_with_simctl
|
358
|
+
launch_simulator
|
359
|
+
|
360
|
+
args = ['simctl', 'install', device.udid, app.path]
|
361
|
+
xcrun.exec(args, log_cmd: true, timeout: 20)
|
362
|
+
|
363
|
+
device.simulator_wait_for_stable_state
|
364
|
+
installed_app_bundle_dir
|
365
|
+
end
|
366
|
+
|
367
|
+
# @!visibility private
|
368
|
+
def wait_for_device_state(target_state)
|
369
|
+
now = Time.now
|
370
|
+
timeout = WAIT_FOR_DEVICE_STATE_OPTS[:timeout]
|
371
|
+
poll_until = now + timeout
|
372
|
+
delay = WAIT_FOR_DEVICE_STATE_OPTS[:interval]
|
373
|
+
in_state = false
|
374
|
+
while Time.now < poll_until
|
375
|
+
in_state = device.update_simulator_state == target_state
|
376
|
+
break if in_state
|
377
|
+
sleep delay
|
378
|
+
end
|
379
|
+
|
380
|
+
elapsed = Time.now - now
|
381
|
+
RunLoop.log_debug("Waited for #{elapsed} seconds for device to have state: '#{target_state}'.")
|
382
|
+
|
383
|
+
unless in_state
|
384
|
+
raise "Expected '#{target_state} but found '#{device.state}' after waiting."
|
385
|
+
end
|
386
|
+
in_state
|
387
|
+
end
|
388
|
+
|
389
|
+
# Required for support of iOS 7 CoreSimulators. Can be removed when
|
390
|
+
# Xcode support is dropped.
|
391
|
+
def sdk_gte_8?
|
392
|
+
device.version >= RunLoop::Version.new('8.0')
|
393
|
+
end
|
394
|
+
|
395
|
+
# The data directory for the the device.
|
396
|
+
#
|
397
|
+
# ~/Library/Developer/CoreSimulator/Devices/<UDID>/data
|
398
|
+
def device_data_dir
|
399
|
+
@device_data_dir ||= File.join(CORE_SIMULATOR_DEVICE_DIR, device.udid, 'data')
|
400
|
+
end
|
401
|
+
|
402
|
+
# The applications directory for the device.
|
403
|
+
#
|
404
|
+
# ~/Library/Developer/CoreSimulator/Devices/<UDID>/Containers/Bundle/Application
|
405
|
+
def device_applications_dir
|
406
|
+
@device_app_dir ||= lambda do
|
407
|
+
if sdk_gte_8?
|
408
|
+
File.join(device_data_dir, 'Containers', 'Bundle', 'Application')
|
409
|
+
else
|
410
|
+
File.join(device_data_dir, 'Applications')
|
411
|
+
end
|
412
|
+
end.call
|
413
|
+
end
|
414
|
+
|
415
|
+
# The sandbox directory for the app.
|
416
|
+
#
|
417
|
+
# ~/Library/Developer/CoreSimulator/Devices/<UDID>/Containers/Data/Application
|
418
|
+
#
|
419
|
+
# Contains Library, Documents, and tmp directories.
|
420
|
+
def app_sandbox_dir
|
421
|
+
app_install_dir = installed_app_bundle_dir
|
422
|
+
return nil if app_install_dir.nil?
|
423
|
+
if sdk_gte_8?
|
424
|
+
app_sandbox_dir_sdk_gte_8
|
425
|
+
else
|
426
|
+
app_install_dir
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def app_sandbox_dir_sdk_gte_8
|
431
|
+
containers_data_dir = File.join(device_data_dir, 'Containers', 'Data', 'Application')
|
432
|
+
apps = Dir.glob("#{containers_data_dir}/**/#{METADATA_PLIST}")
|
433
|
+
match = apps.find do |metadata_plist|
|
434
|
+
pbuddy.plist_read('MCMMetadataIdentifier', metadata_plist) == app.bundle_identifier
|
435
|
+
end
|
436
|
+
if match
|
437
|
+
File.dirname(match)
|
438
|
+
else
|
439
|
+
nil
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
# The Library directory in the sandbox.
|
444
|
+
def app_library_dir
|
445
|
+
base_dir = app_sandbox_dir
|
446
|
+
if base_dir.nil?
|
447
|
+
nil
|
448
|
+
else
|
449
|
+
File.join(base_dir, 'Library')
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
# The Library/Preferences directory in the sandbox.
|
454
|
+
def app_library_preferences_dir
|
455
|
+
base_dir = app_library_dir
|
456
|
+
if base_dir.nil?
|
457
|
+
nil
|
458
|
+
else
|
459
|
+
File.join(base_dir, 'Preferences')
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
# The Documents directory in the sandbox.
|
464
|
+
def app_documents_dir
|
465
|
+
base_dir = app_sandbox_dir
|
466
|
+
if base_dir.nil?
|
467
|
+
nil
|
468
|
+
else
|
469
|
+
File.join(base_dir, 'Documents')
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
# The tmp directory in the sandbox.
|
474
|
+
def app_tmp_dir
|
475
|
+
base_dir = app_sandbox_dir
|
476
|
+
if base_dir.nil?
|
477
|
+
nil
|
478
|
+
else
|
479
|
+
File.join(base_dir, 'tmp')
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
# A cache of installed apps on the device.
|
484
|
+
def device_caches_dir
|
485
|
+
@device_caches_dir ||= File.join(device_data_dir, 'Library', 'Caches')
|
486
|
+
end
|
487
|
+
|
488
|
+
# Required after when installing and uninstalling.
|
489
|
+
def clear_device_launch_csstore
|
490
|
+
glob = File.join(device_caches_dir, "com.apple.LaunchServices-*.csstore")
|
491
|
+
Dir.glob(glob) do | ccstore |
|
492
|
+
FileUtils.rm_f ccstore
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
# The sha1 of the installed app.
|
497
|
+
def installed_app_sha1
|
498
|
+
installed_bundle = installed_app_bundle_dir
|
499
|
+
if installed_bundle
|
500
|
+
RunLoop::Directory.directory_digest(installed_bundle)
|
501
|
+
else
|
502
|
+
nil
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
# Is the app that is install the same as the one we have in hand?
|
507
|
+
def same_sha1_as_installed?
|
508
|
+
app.sha1 == installed_app_sha1
|
509
|
+
end
|
510
|
+
|
511
|
+
# Returns the path to the installed app bundle directory (.app).
|
512
|
+
#
|
513
|
+
# If this method returns nil, the app is not installed.
|
514
|
+
def installed_app_bundle_dir
|
515
|
+
sim_app_dir = device_applications_dir
|
516
|
+
return nil if !File.exist?(sim_app_dir)
|
517
|
+
Dir.glob("#{sim_app_dir}/**/*.app").find do |path|
|
518
|
+
RunLoop::App.new(path).bundle_identifier == app.bundle_identifier
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
# 1. Does nothing if the app is not installed.
|
523
|
+
# 2. Does nothing if the app the same as the app that is installed
|
524
|
+
# 3. Installs app if it is different from the installed app
|
525
|
+
#
|
526
|
+
def ensure_app_same
|
527
|
+
installed_app_bundle = installed_app_bundle_dir
|
528
|
+
|
529
|
+
if !installed_app_bundle
|
530
|
+
RunLoop.log_debug("App: #{app} is not installed")
|
531
|
+
return true
|
532
|
+
end
|
533
|
+
|
534
|
+
installed_sha = installed_app_sha1
|
535
|
+
app_sha = app.sha1
|
536
|
+
|
537
|
+
if installed_sha == app_sha
|
538
|
+
RunLoop.log_debug("Installed app is the same as #{app}")
|
539
|
+
return true
|
540
|
+
end
|
541
|
+
|
542
|
+
RunLoop.log_debug("The app you are are testing is not the same as the app that is installed.")
|
543
|
+
RunLoop.log_debug(" Installed app SHA: #{installed_sha}")
|
544
|
+
RunLoop.log_debug(" App to launch SHA: #{app_sha}")
|
545
|
+
RunLoop.log_debug("Will install #{app}")
|
546
|
+
|
547
|
+
FileUtils.rm_rf installed_app_bundle
|
548
|
+
RunLoop.log_debug('Deleted the existing app')
|
549
|
+
|
550
|
+
directory = File.expand_path(File.join(installed_app_bundle, '..'))
|
551
|
+
bundle_name = File.basename(app.path)
|
552
|
+
target = File.join(directory, bundle_name)
|
553
|
+
|
554
|
+
args = ['ditto', app.path, target]
|
555
|
+
xcrun.exec(args, log_cmd: true)
|
556
|
+
|
557
|
+
RunLoop.log_debug("Installed #{app} on CoreSimulator #{device.udid}")
|
558
|
+
|
559
|
+
clear_device_launch_csstore
|
560
|
+
|
561
|
+
true
|
562
|
+
end
|
563
|
+
|
564
|
+
# Shared tasks across CoreSimulators iOS 7 and > iOS 7
|
565
|
+
def reset_app_sandbox_internal_shared
|
566
|
+
[app_documents_dir, app_tmp_dir].each do |dir|
|
567
|
+
FileUtils.rm_rf dir
|
568
|
+
FileUtils.mkdir dir
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
# @!visibility private
|
573
|
+
def reset_app_sandbox_internal_sdk_gte_8
|
574
|
+
lib_dir = app_library_dir
|
575
|
+
RunLoop::Directory.recursive_glob_for_entries(lib_dir).each do |entry|
|
576
|
+
if entry.include?('Preferences')
|
577
|
+
# nop
|
578
|
+
else
|
579
|
+
if File.exist?(entry)
|
580
|
+
FileUtils.rm_rf(entry)
|
581
|
+
end
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
prefs_dir = app_library_preferences_dir
|
586
|
+
protected = ['com.apple.UIAutomation.plist',
|
587
|
+
'com.apple.UIAutomationPlugIn.plist']
|
588
|
+
RunLoop::Directory.recursive_glob_for_entries(prefs_dir).each do |entry|
|
589
|
+
unless protected.include?(File.basename(entry))
|
590
|
+
if File.exist?(entry)
|
591
|
+
FileUtils.rm_rf entry
|
592
|
+
end
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
# @!visibility private
|
598
|
+
def reset_app_sandbox_internal_sdk_lt_8
|
599
|
+
prefs_dir = app_library_preferences_dir
|
600
|
+
RunLoop::Directory.recursive_glob_for_entries(prefs_dir).each do |entry|
|
601
|
+
if entry.end_with?('.GlobalPreferences.plist') ||
|
602
|
+
entry.end_with?('com.apple.PeoplePicker.plist')
|
603
|
+
# nop
|
604
|
+
else
|
605
|
+
if File.exist?(entry)
|
606
|
+
FileUtils.rm_rf entry
|
607
|
+
end
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
# app preferences lives in device Library/Preferences
|
612
|
+
device_prefs_dir = File.join(app_sandbox_dir, 'Library', 'Preferences')
|
613
|
+
app_prefs_plist = File.join(device_prefs_dir, "#{app.bundle_identifier}.plist")
|
614
|
+
if File.exist?(app_prefs_plist)
|
615
|
+
FileUtils.rm_rf(app_prefs_plist)
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
# @!visibility private
|
620
|
+
def reset_app_sandbox_internal
|
621
|
+
reset_app_sandbox_internal_shared
|
622
|
+
|
623
|
+
if sdk_gte_8?
|
624
|
+
reset_app_sandbox_internal_sdk_gte_8
|
625
|
+
else
|
626
|
+
reset_app_sandbox_internal_sdk_lt_8
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
# Not yet. Failing on Travis and this is not a feature yet.
|
631
|
+
#
|
632
|
+
# There is a spec that has been commented out.
|
633
|
+
# @!visibility private
|
634
|
+
# TODO Command line tool
|
635
|
+
# def app_uia_crash_logs
|
636
|
+
# base_dir = app_library_dir
|
637
|
+
# if base_dir.nil?
|
638
|
+
# nil
|
639
|
+
# else
|
640
|
+
# dir = File.join(base_dir, 'CrashReporter', 'UIALogs')
|
641
|
+
# if Dir.exist?(dir)
|
642
|
+
# Dir.glob("#{dir}/*.plist")
|
643
|
+
# else
|
644
|
+
# nil
|
645
|
+
# end
|
646
|
+
# end
|
647
|
+
# end
|
648
|
+
end
|
data/lib/run_loop/device.rb
CHANGED
@@ -271,9 +271,7 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
271
271
|
# TODO needs unit tests.
|
272
272
|
def simulator_data_dir_size
|
273
273
|
path = File.join(simulator_root_dir, 'data')
|
274
|
-
|
275
|
-
hash = xcrun.exec(args)
|
276
|
-
hash[:out].split(' ').first.to_i
|
274
|
+
RunLoop::Directory.size(path, :mb)
|
277
275
|
end
|
278
276
|
|
279
277
|
# @!visibility private
|
@@ -316,13 +314,11 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
316
314
|
current_sha = nil
|
317
315
|
sha_fn = lambda do |data_dir|
|
318
316
|
begin
|
319
|
-
#
|
320
|
-
# returns in < 0.3 seconds.
|
317
|
+
# Typically, this returns in < 0.3 seconds.
|
321
318
|
Timeout.timeout(2, TimeoutError) do
|
322
319
|
RunLoop::Directory.directory_digest(data_dir)
|
323
320
|
end
|
324
|
-
rescue =>
|
325
|
-
RunLoop.log_error(e) if RunLoop::Environment.debug?
|
321
|
+
rescue => _
|
326
322
|
SecureRandom.uuid
|
327
323
|
end
|
328
324
|
end
|
@@ -387,7 +383,11 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
387
383
|
io.close if io && !io.closed?
|
388
384
|
end
|
389
385
|
|
390
|
-
line
|
386
|
+
if line
|
387
|
+
line.chomp
|
388
|
+
else
|
389
|
+
line
|
390
|
+
end
|
391
391
|
end
|
392
392
|
|
393
393
|
# @!visibility private
|