run_loop 1.5.1 → 1.5.2
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.rb +2 -0
- data/lib/run_loop/app.rb +5 -0
- data/lib/run_loop/cli/instruments.rb +0 -1
- data/lib/run_loop/cli/simctl.rb +98 -13
- data/lib/run_loop/core.rb +194 -110
- data/lib/run_loop/device.rb +218 -2
- data/lib/run_loop/directory.rb +22 -5
- data/lib/run_loop/environment.rb +10 -0
- data/lib/run_loop/instruments.rb +55 -72
- data/lib/run_loop/life_cycle/core_simulator.rb +544 -0
- data/lib/run_loop/logging.rb +68 -0
- data/lib/run_loop/simctl/bridge.rb +15 -2
- data/lib/run_loop/version.rb +1 -1
- data/lib/run_loop/xcode.rb +15 -0
- data/lib/run_loop/xcrun.rb +75 -0
- data/lib/run_loop/xctools.rb +19 -0
- metadata +5 -16
data/lib/run_loop/device.rb
CHANGED
@@ -103,9 +103,9 @@ Please update your sources.))
|
|
103
103
|
# @!visibility private
|
104
104
|
def to_s
|
105
105
|
if simulator?
|
106
|
-
"#<Simulator: #{name} #{udid} #{instruction_set}>"
|
106
|
+
"#<Simulator: #{name} (#{version.to_s}) #{udid} #{instruction_set}>"
|
107
107
|
else
|
108
|
-
"#<Device: #{name} #{udid}>"
|
108
|
+
"#<Device: #{name} (#{version.to_s}) #{udid}>"
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
@@ -188,6 +188,28 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
188
188
|
end
|
189
189
|
end
|
190
190
|
|
191
|
+
# @!visibility private
|
192
|
+
# The device `state` is reported by the simctl tool.
|
193
|
+
#
|
194
|
+
# The expected values from simctl are:
|
195
|
+
#
|
196
|
+
# * Booted
|
197
|
+
# * Shutdown
|
198
|
+
# * Shutting Down
|
199
|
+
#
|
200
|
+
# To handle exceptional cases, there are these two additional states:
|
201
|
+
#
|
202
|
+
# * Unavailable # Should never occur
|
203
|
+
# * Unknown # A stub for future changes
|
204
|
+
def update_simulator_state
|
205
|
+
if physical_device?
|
206
|
+
raise RuntimeError, 'This method is available only for simulators'
|
207
|
+
end
|
208
|
+
|
209
|
+
@state = fetch_simulator_state
|
210
|
+
end
|
211
|
+
|
212
|
+
# @!visibility private
|
191
213
|
def simulator_root_dir
|
192
214
|
@simulator_root_dir ||= lambda {
|
193
215
|
return nil if physical_device?
|
@@ -195,6 +217,7 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
195
217
|
}.call
|
196
218
|
end
|
197
219
|
|
220
|
+
# @!visibility private
|
198
221
|
def simulator_accessibility_plist_path
|
199
222
|
@simulator_accessibility_plist_path ||= lambda {
|
200
223
|
return nil if physical_device?
|
@@ -202,6 +225,7 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
202
225
|
}.call
|
203
226
|
end
|
204
227
|
|
228
|
+
# @!visibility private
|
205
229
|
def simulator_preferences_plist_path
|
206
230
|
@simulator_preferences_plist_path ||= lambda {
|
207
231
|
return nil if physical_device?
|
@@ -209,6 +233,7 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
209
233
|
}.call
|
210
234
|
end
|
211
235
|
|
236
|
+
# @!visibility private
|
212
237
|
def simulator_log_file_path
|
213
238
|
@simulator_log_file_path ||= lambda {
|
214
239
|
return nil if physical_device?
|
@@ -216,9 +241,200 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
216
241
|
}.call
|
217
242
|
end
|
218
243
|
|
244
|
+
# @!visibility private
|
245
|
+
def simulator_device_plist
|
246
|
+
@simulator_device_plist ||= lambda do
|
247
|
+
return nil if physical_device?
|
248
|
+
File.join(simulator_root_dir, 'device.plist')
|
249
|
+
end.call
|
250
|
+
end
|
251
|
+
|
252
|
+
# @!visibility private
|
253
|
+
# Is this the first launch of this Simulator?
|
254
|
+
#
|
255
|
+
# TODO Needs unit and integration tests.
|
256
|
+
def simulator_first_launch?
|
257
|
+
megabytes = simulator_data_dir_size
|
258
|
+
|
259
|
+
if version >= RunLoop::Version.new('9.0')
|
260
|
+
megabytes < 20
|
261
|
+
elsif version >= RunLoop::Version.new('8.0')
|
262
|
+
megabytes < 12
|
263
|
+
else
|
264
|
+
megabytes < 8
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# @!visibility private
|
269
|
+
# The size of the simulator data/ directory.
|
270
|
+
#
|
271
|
+
# TODO needs unit tests.
|
272
|
+
def simulator_data_dir_size
|
273
|
+
path = File.join(simulator_root_dir, 'data')
|
274
|
+
args = ['du', '-m', '-d', '0', path]
|
275
|
+
hash = xcrun.exec(args)
|
276
|
+
hash[:out].split(' ').first.to_i
|
277
|
+
end
|
278
|
+
|
279
|
+
# @!visibility private
|
280
|
+
#
|
281
|
+
# Waits for three conditions:
|
282
|
+
#
|
283
|
+
# 1. The SHA sum of the simulator data/ directory to be stable.
|
284
|
+
# 2. No more log messages are begin generated
|
285
|
+
# 3. 1 and 2 must hold for 2 seconds.
|
286
|
+
#
|
287
|
+
# When the simulator version is >= iOS 9 _and_ it is the first launch of
|
288
|
+
# the simulator after a reset or a new simulator install, a fourth condition
|
289
|
+
# is added:
|
290
|
+
#
|
291
|
+
# 4. The first three conditions must be met a second time.
|
292
|
+
def simulator_wait_for_stable_state
|
293
|
+
require 'securerandom'
|
294
|
+
|
295
|
+
quiet_time = 2
|
296
|
+
delay = 0.5
|
297
|
+
|
298
|
+
first_launch = false
|
299
|
+
|
300
|
+
if version >= RunLoop::Version.new('9.0')
|
301
|
+
first_launch = simulator_data_dir_size < 20
|
302
|
+
end
|
303
|
+
|
304
|
+
now = Time.now
|
305
|
+
timeout = 30
|
306
|
+
poll_until = now + timeout
|
307
|
+
quiet = now + quiet_time
|
308
|
+
|
309
|
+
is_stable = false
|
310
|
+
|
311
|
+
path = File.join(simulator_root_dir, 'data')
|
312
|
+
current_sha = nil
|
313
|
+
sha_fn = lambda do |data_dir|
|
314
|
+
begin
|
315
|
+
# Directory.directory_digest has a blocking read. Typically, it
|
316
|
+
# returns in < 0.3 seconds.
|
317
|
+
Timeout.timeout(2, TimeoutError) do
|
318
|
+
RunLoop::Directory.directory_digest(data_dir)
|
319
|
+
end
|
320
|
+
rescue => e
|
321
|
+
RunLoop.log_error(e) if RunLoop::Environment.debug?
|
322
|
+
SecureRandom.uuid
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
current_line = nil
|
327
|
+
|
328
|
+
while Time.now < poll_until do
|
329
|
+
latest_sha = sha_fn.call(path)
|
330
|
+
latest_line = last_line_from_simulator_log_file
|
331
|
+
|
332
|
+
is_stable = current_sha == latest_sha && current_line == latest_line
|
333
|
+
|
334
|
+
if is_stable
|
335
|
+
if Time.now > quiet
|
336
|
+
if first_launch
|
337
|
+
RunLoop.log_debug('First launch detected - allowing additional time to stabilize')
|
338
|
+
first_launch = false
|
339
|
+
sleep 1.2
|
340
|
+
quiet = Time.now + quiet_time
|
341
|
+
else
|
342
|
+
break
|
343
|
+
end
|
344
|
+
else
|
345
|
+
quiet = Time.now + quiet_time
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
current_sha = latest_sha
|
350
|
+
current_line = latest_line
|
351
|
+
sleep delay
|
352
|
+
end
|
353
|
+
|
354
|
+
if is_stable
|
355
|
+
elapsed = Time.now - now
|
356
|
+
stabilized = elapsed - quiet_time
|
357
|
+
RunLoop.log_debug("Simulator stable after #{stabilized} seconds")
|
358
|
+
RunLoop.log_debug("Waited a total of #{elapsed} seconds for simulator to stabilize")
|
359
|
+
else
|
360
|
+
RunLoop.log_debug("Timed out: simulator not stable after #{timeout} seconds")
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
219
364
|
private
|
220
365
|
|
366
|
+
# @!visibility private
|
367
|
+
# TODO write a unit test.
|
368
|
+
def last_line_from_simulator_log_file
|
369
|
+
file = simulator_log_file_path
|
370
|
+
|
371
|
+
return nil if !File.exist?(file)
|
372
|
+
|
373
|
+
debug = RunLoop::Environment.debug?
|
374
|
+
|
375
|
+
begin
|
376
|
+
io = File.open(file, 'r')
|
377
|
+
io.seek(-100, IO::SEEK_END)
|
378
|
+
|
379
|
+
line = io.readline
|
380
|
+
rescue StandardError => e
|
381
|
+
RunLoop.log_error("Caught #{e} while reading simulator log file") if debug
|
382
|
+
ensure
|
383
|
+
io.close if io && !io.closed?
|
384
|
+
end
|
385
|
+
|
386
|
+
line
|
387
|
+
end
|
388
|
+
|
389
|
+
# @!visibility private
|
390
|
+
def xcrun
|
391
|
+
RunLoop::Xcrun.new
|
392
|
+
end
|
393
|
+
|
394
|
+
# @!visibility private
|
395
|
+
def detect_state_from_line(line)
|
396
|
+
|
397
|
+
if line[/unavailable/, 0]
|
398
|
+
RunLoop.log_debug("Simulator state is unavailable: #{line}")
|
399
|
+
return 'Unavailable'
|
400
|
+
end
|
401
|
+
|
402
|
+
state = line[/(Booted|Shutdown|Shutting Down)/,0]
|
403
|
+
|
404
|
+
if state.nil?
|
405
|
+
RunLoop.log_debug("Simulator state is unknown: #{line}")
|
406
|
+
'Unknown'
|
407
|
+
else
|
408
|
+
state
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
# @!visibility private
|
413
|
+
def fetch_simulator_state
|
414
|
+
if physical_device?
|
415
|
+
raise RuntimeError, 'This method is available only for simulators'
|
416
|
+
end
|
417
|
+
|
418
|
+
args = ['simctl', 'list', 'devices']
|
419
|
+
hash = xcrun.exec(args)
|
420
|
+
out = hash[:out]
|
421
|
+
|
422
|
+
matched_line = out.split("\n").find do |line|
|
423
|
+
line.include?(udid)
|
424
|
+
end
|
425
|
+
|
426
|
+
if matched_line.nil?
|
427
|
+
raise RuntimeError,
|
428
|
+
"Expected a simulator with udid '#{udid}', but found none"
|
429
|
+
end
|
430
|
+
|
431
|
+
detect_state_from_line(matched_line)
|
432
|
+
end
|
433
|
+
|
434
|
+
# @!visibility private
|
221
435
|
CORE_SIMULATOR_DEVICE_DIR = File.expand_path('~/Library/Developer/CoreSimulator/Devices')
|
436
|
+
|
437
|
+
# @!visibility private
|
222
438
|
CORE_SIMULATOR_LOGS_DIR = File.expand_path('~/Library/Logs/CoreSimulator')
|
223
439
|
|
224
440
|
# TODO Is this a good idea? It speeds up rspec tests by a factor of ~2x...
|
data/lib/run_loop/directory.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require 'digest'
|
2
|
-
require 'openssl'
|
3
|
-
|
4
1
|
module RunLoop
|
5
2
|
|
6
3
|
# Class for performing operations on directories.
|
7
4
|
class Directory
|
5
|
+
require 'digest'
|
6
|
+
require 'openssl'
|
7
|
+
require 'pathname'
|
8
8
|
|
9
9
|
# Dir.glob ignores files that start with '.', but we often need to find
|
10
10
|
# dotted files and directories.
|
@@ -39,10 +39,27 @@ module RunLoop
|
|
39
39
|
raise ArgumentError, "Expected a non-empty dir at '#{path}' found '#{entries}'"
|
40
40
|
end
|
41
41
|
|
42
|
+
debug = RunLoop::Environment.debug?
|
43
|
+
|
42
44
|
sha = OpenSSL::Digest::SHA256.new
|
43
45
|
self.recursive_glob_for_entries(path).each do |file|
|
44
|
-
|
45
|
-
|
46
|
+
if File.directory?(file)
|
47
|
+
# skip directories
|
48
|
+
elsif !Pathname.new(file).exist?
|
49
|
+
# skip broken symlinks
|
50
|
+
else
|
51
|
+
case File.ftype(file)
|
52
|
+
when 'fifo'
|
53
|
+
RunLoop.log_warn("SHA1 SKIPPING FIFO #{file}") if debug
|
54
|
+
when 'socket'
|
55
|
+
RunLoop.log_warn("SHA1 SKIPPING SOCKET #{file}") if debug
|
56
|
+
when 'characterSpecial'
|
57
|
+
RunLoop.log_warn("SHA1 SKIPPING CHAR SPECIAL #{file}") if debug
|
58
|
+
when 'blockSpecial'
|
59
|
+
RunLoop.log_warn("SHA1 SKIPPING BLOCK SPECIAL #{file}") if debug
|
60
|
+
else
|
61
|
+
sha << File.read(file)
|
62
|
+
end
|
46
63
|
end
|
47
64
|
end
|
48
65
|
sha.hexdigest
|
data/lib/run_loop/environment.rb
CHANGED
data/lib/run_loop/instruments.rb
CHANGED
@@ -13,6 +13,10 @@ module RunLoop
|
|
13
13
|
@xcode ||= RunLoop::Xcode.new
|
14
14
|
end
|
15
15
|
|
16
|
+
def xcrun
|
17
|
+
RunLoop::Xcrun.new
|
18
|
+
end
|
19
|
+
|
16
20
|
# @!visibility private
|
17
21
|
def to_s
|
18
22
|
"#<Instruments #{version.to_s}>"
|
@@ -106,10 +110,10 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
106
110
|
# @return [RunLoop::Version] A version object.
|
107
111
|
def version
|
108
112
|
@instruments_version ||= lambda do
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
+
args = ['instruments']
|
114
|
+
hash = xcrun.exec(args, log_cmd: true)
|
115
|
+
version_str = hash[:err][VERSION_REGEX, 0]
|
116
|
+
RunLoop::Version.new(version_str)
|
113
117
|
end.call
|
114
118
|
end
|
115
119
|
|
@@ -129,54 +133,41 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
129
133
|
# @return [Array<String>] Instruments.app templates.
|
130
134
|
def templates
|
131
135
|
@instruments_templates ||= lambda do
|
136
|
+
args = ['instruments', '-s', 'templates']
|
137
|
+
hash = xcrun.exec(args, log_cmd: true)
|
132
138
|
if xcode.version_gte_6?
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
else
|
140
|
-
stripped
|
141
|
-
end
|
142
|
-
end.compact
|
143
|
-
end
|
144
|
-
elsif xcode.version_gte_51?
|
145
|
-
execute_command(['-s', 'templates']) do |stdout, stderr, _|
|
146
|
-
err = stderr.read
|
147
|
-
if !err.nil? || err != ''
|
148
|
-
$stderr.puts stderr.read
|
139
|
+
hash[:out].chomp.split("\n").map do |elm|
|
140
|
+
stripped = elm.strip.tr('"', '')
|
141
|
+
if stripped == '' || stripped == 'Known Templates:'
|
142
|
+
nil
|
143
|
+
else
|
144
|
+
stripped
|
149
145
|
end
|
150
|
-
|
151
|
-
stdout.read.strip.split("\n").delete_if do |path|
|
152
|
-
not path =~ /tracetemplate/
|
153
|
-
end.map { |elm| elm.strip }
|
154
|
-
end
|
146
|
+
end.compact
|
155
147
|
else
|
156
|
-
|
148
|
+
hash[:out].strip.split("\n").delete_if do |path|
|
149
|
+
not path =~ /tracetemplate/
|
150
|
+
end.map { |elm| elm.strip }
|
157
151
|
end
|
158
152
|
end.call
|
159
153
|
end
|
160
154
|
|
161
|
-
# Returns an array the available physical devices.
|
155
|
+
# Returns an array of the available physical devices.
|
162
156
|
#
|
163
157
|
# @return [Array<RunLoop::Device>] All the devices will be physical
|
164
158
|
# devices.
|
165
159
|
def physical_devices
|
166
160
|
@instruments_physical_devices ||= lambda do
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
end
|
173
|
-
valid.map do |device|
|
174
|
-
udid = device[DEVICE_UDID_REGEX, 0]
|
175
|
-
version = device[VERSION_REGEX, 0]
|
176
|
-
name = device.split('(').first.strip
|
161
|
+
fetch_devices[:out].chomp.split("\n").map do |line|
|
162
|
+
udid = line[DEVICE_UDID_REGEX, 0]
|
163
|
+
if udid
|
164
|
+
version = line[VERSION_REGEX, 0]
|
165
|
+
name = line.split('(').first.strip
|
177
166
|
RunLoop::Device.new(name, version, udid)
|
167
|
+
else
|
168
|
+
nil
|
178
169
|
end
|
179
|
-
end
|
170
|
+
end.compact
|
180
171
|
end.call
|
181
172
|
end
|
182
173
|
|
@@ -195,35 +186,39 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
195
186
|
# @return [Array<RunLoop::Device>] All the devices will be simulators.
|
196
187
|
def simulators
|
197
188
|
@instruments_simulators ||= lambda do
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
if line_is_xcode5_simulator?(stripped)
|
209
|
-
name = line
|
210
|
-
udid = line
|
211
|
-
else
|
212
|
-
name = stripped.split('(').first.strip
|
213
|
-
udid = line[CORE_SIMULATOR_UDID_REGEX, 0]
|
214
|
-
end
|
215
|
-
|
216
|
-
RunLoop::Device.new(name, version, udid)
|
189
|
+
fetch_devices[:out].chomp.split("\n").map do |line|
|
190
|
+
stripped = line.strip
|
191
|
+
if line_is_simulator?(stripped) &&
|
192
|
+
!line_is_simulator_paired_with_watch?(stripped)
|
193
|
+
|
194
|
+
version = stripped[VERSION_REGEX, 0]
|
195
|
+
|
196
|
+
if line_is_xcode5_simulator?(stripped)
|
197
|
+
name = line
|
198
|
+
udid = line
|
217
199
|
else
|
218
|
-
|
200
|
+
name = stripped.split('(').first.strip
|
201
|
+
udid = line[CORE_SIMULATOR_UDID_REGEX, 0]
|
219
202
|
end
|
220
|
-
|
221
|
-
|
203
|
+
|
204
|
+
RunLoop::Device.new(name, version, udid)
|
205
|
+
else
|
206
|
+
nil
|
207
|
+
end
|
208
|
+
end.compact
|
222
209
|
end.call
|
223
210
|
end
|
224
211
|
|
225
212
|
private
|
226
213
|
|
214
|
+
# @!visibility private
|
215
|
+
def fetch_devices
|
216
|
+
@device_hash ||= lambda do
|
217
|
+
args = ['instruments', '-s', 'devices']
|
218
|
+
xcrun.exec(args, log_cmd: true)
|
219
|
+
end.call
|
220
|
+
end
|
221
|
+
|
227
222
|
# @!visibility private
|
228
223
|
#
|
229
224
|
# ```
|
@@ -354,18 +349,6 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
354
349
|
end
|
355
350
|
end
|
356
351
|
|
357
|
-
# @!visibility private
|
358
|
-
#
|
359
|
-
# Filters `instruments` spam.
|
360
|
-
def filter_stderr_spam(stderr)
|
361
|
-
# Xcode 6 GM is spamming "WebKit Threading Violations"
|
362
|
-
stderr.read.strip.split("\n").each do |line|
|
363
|
-
unless line[/WebKit Threading Violation/, 0]
|
364
|
-
$stderr.puts line
|
365
|
-
end
|
366
|
-
end
|
367
|
-
end
|
368
|
-
|
369
352
|
# @!visibility private
|
370
353
|
def line_is_simulator?(line)
|
371
354
|
line_is_core_simulator?(line) || line_is_xcode5_simulator?(line)
|