xcknife 0.11.1 → 0.12.0

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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'pp'
3
5
  require 'fileutils'
@@ -8,6 +10,7 @@ require 'logger'
8
10
  require 'shellwords'
9
11
  require 'open3'
10
12
  require 'xcknife/exceptions'
13
+ require 'etc'
11
14
 
12
15
  module XCKnife
13
16
  class TestDumper
@@ -27,7 +30,7 @@ module XCKnife
27
30
  @skip_dump_bundle_names = []
28
31
  @simctl_timeout = 0
29
32
  parse_arguments(args)
30
- @device_id ||= "booted"
33
+ @device_id ||= 'booted'
31
34
  @logger = logger
32
35
  @logger.level = @debug ? Logger::DEBUG : Logger::FATAL
33
36
  @parser = nil
@@ -38,70 +41,61 @@ module XCKnife
38
41
  naive_dump_bundle_names: @naive_dump_bundle_names, skip_dump_bundle_names: @skip_dump_bundle_names, simctl_timeout: @simctl_timeout)
39
42
  extra_environment_variables = parse_scheme_file
40
43
  logger.info { "Environment variables from xcscheme: #{extra_environment_variables.pretty_inspect}" }
41
- output_fd = File.open(@output_file, "w")
44
+ output_fd = File.open(@output_file, 'w')
42
45
  if @temporary_output_folder.nil?
43
- Dir.mktmpdir("xctestdumper_") do |outfolder|
46
+ Dir.mktmpdir('xctestdumper_') do |outfolder|
44
47
  list_tests(extra_environment_variables, helper, outfolder, output_fd)
45
48
  end
46
49
  else
47
- unless File.directory?(@temporary_output_folder)
48
- raise TestDumpError, "Error no such directory: #{@temporary_output_folder}"
49
- end
50
+ raise TestDumpError, "Error no such directory: #{@temporary_output_folder}" unless File.directory?(@temporary_output_folder)
50
51
 
51
- if Dir.entries(@temporary_output_folder).any? { |f| File.file?(File.join(@temporary_output_folder,f)) }
52
- puts "Warning: #{@temporary_output_folder} is not empty! Files can be overwritten."
53
- end
52
+ puts "Warning: #{@temporary_output_folder} is not empty! Files can be overwritten." if Dir.entries(@temporary_output_folder).any? { |f| File.file?(File.join(@temporary_output_folder, f)) }
54
53
  list_tests(extra_environment_variables, helper, File.absolute_path(@temporary_output_folder), output_fd)
55
54
  end
56
55
  output_fd.close
57
- puts "Done listing test methods"
56
+ puts 'Done listing test methods'
58
57
  end
59
58
 
60
59
  private
60
+
61
61
  def list_tests(extra_environment_variables, helper, outfolder, output_fd)
62
62
  helper.call(@derived_data_folder, outfolder, extra_environment_variables).each do |test_specification|
63
63
  concat_to_file(test_specification, output_fd)
64
64
  end
65
65
  end
66
66
 
67
-
68
67
  def parse_scheme_file
69
68
  return {} unless @xcscheme_file
70
- unless File.exists?(@xcscheme_file)
71
- raise ArgumentError, "Error: no such xcscheme file: #{@xcscheme_file}"
72
- end
69
+ raise ArgumentError, "Error: no such xcscheme file: #{@xcscheme_file}" unless File.exist?(@xcscheme_file)
70
+
73
71
  XCKnife::XcschemeAnalyzer.extract_environment_variables(IO.read(@xcscheme_file))
74
72
  end
75
73
 
76
74
  def parse_arguments(args)
77
75
  positional_arguments = parse_options(args)
78
- if positional_arguments.size < required_arguments.size
79
- warn_and_exit("You must specify *all* required arguments: #{required_arguments.join(", ")}")
80
- end
76
+ warn_and_exit("You must specify *all* required arguments: #{required_arguments.join(', ')}") if positional_arguments.size < required_arguments.size
81
77
  @derived_data_folder, @output_file, @device_id = positional_arguments
82
78
  end
83
79
 
84
80
  def parse_options(args)
85
- begin
86
- return @parser.parse(args)
87
- rescue OptionParser::ParseError => error
88
- warn_and_exit(error)
89
- end
81
+ @parser.parse(args)
82
+ rescue OptionParser::ParseError => e
83
+ warn_and_exit(e)
90
84
  end
91
85
 
92
86
  def build_parser
93
87
  OptionParser.new do |opts|
94
88
  opts.banner += " #{arguments_banner}"
95
- opts.on("-d", "--debug", "Debug mode enabled") { |v| @debug = v }
96
- opts.on("-r", "--retry-count COUNT", "Max retry count for simulator output", Integer) { |v| @max_retry_count = v }
97
- opts.on("-x", '--simctl-timeout SECONDS', "Max allowed time in seconds for simctl commands", Integer) { |v| @simctl_timeout = v }
98
- opts.on("-t", "--temporary-output OUTPUT_FOLDER", "Sets temporary Output folder") { |v| @temporary_output_folder = v }
99
- opts.on("-s", "--scheme XCSCHEME_FILE", "Reads environments variables from the xcscheme file") { |v| @xcscheme_file = v }
100
- opts.on("-l", "--dylib_logfile DYLIB_LOG_FILE", "Path for dylib log file") { |v| @dylib_logfile_path = v }
89
+ opts.on('-d', '--debug', 'Debug mode enabled') { |v| @debug = v }
90
+ opts.on('-r', '--retry-count COUNT', 'Max retry count for simulator output', Integer) { |v| @max_retry_count = v }
91
+ opts.on('-x', '--simctl-timeout SECONDS', 'Max allowed time in seconds for simctl commands', Integer) { |v| @simctl_timeout = v }
92
+ opts.on('-t', '--temporary-output OUTPUT_FOLDER', 'Sets temporary Output folder') { |v| @temporary_output_folder = v }
93
+ opts.on('-s', '--scheme XCSCHEME_FILE', 'Reads environments variables from the xcscheme file') { |v| @xcscheme_file = v }
94
+ opts.on('-l', '--dylib_logfile DYLIB_LOG_FILE', 'Path for dylib log file') { |v| @dylib_logfile_path = v }
101
95
  opts.on('--naive-dump TEST_BUNDLE_NAMES', 'List of test bundles to dump using static analysis', Array) { |v| @naive_dump_bundle_names = v }
102
96
  opts.on('--skip-dump TEST_BUNDLE_NAMES', 'List of test bundles to skip dumping', Array) { |v| @skip_dump_bundle_names = v }
103
97
 
104
- opts.on_tail("-h", "--help", "Show this message") do
98
+ opts.on_tail('-h', '--help', 'Show this message') do
105
99
  puts opts
106
100
  exit
107
101
  end
@@ -118,7 +112,7 @@ module XCKnife
118
112
 
119
113
  def arguments_banner
120
114
  optional_args = optional_arguments.map { |a| "[#{a}]" }
121
- (required_arguments + optional_args).join(" ")
115
+ (required_arguments + optional_args).join(' ')
122
116
  end
123
117
 
124
118
  def warn_and_exit(msg)
@@ -128,7 +122,7 @@ module XCKnife
128
122
  def concat_to_file(test_specification, output_fd)
129
123
  file = test_specification.json_stream_file
130
124
  IO.readlines(file).each do |line|
131
- event = OpenStruct.new(JSON.load(line))
125
+ event = OpenStruct.new(JSON.parse(line))
132
126
  if should_test_event_be_ignored?(test_specification, event)
133
127
  logger.info "Skipped test dumper line #{line}"
134
128
  else
@@ -141,8 +135,9 @@ module XCKnife
141
135
 
142
136
  # Current limitation: this only supports class level skipping
143
137
  def should_test_event_be_ignored?(test_specification, event)
144
- return false unless event["test"] == "1"
145
- test_specification.skip_test_identifiers.include?(event["className"])
138
+ return false unless event['test'] == '1'
139
+
140
+ test_specification.skip_test_identifiers.include?(event['className'])
146
141
  end
147
142
  end
148
143
 
@@ -151,15 +146,16 @@ module XCKnife
151
146
 
152
147
  attr_reader :logger
153
148
 
149
+ # rubocop:disable Metrics/ParameterLists
154
150
  def initialize(device_id, max_retry_count, debug, logger, dylib_logfile_path,
155
151
  naive_dump_bundle_names: [], skip_dump_bundle_names: [], simctl_timeout: 0)
156
152
  @xcode_path = `xcode-select -p`.strip
157
153
  @simctl_path = `xcrun -f simctl`.strip
158
154
  @nm_path = `xcrun -f nm`.strip
159
155
  @swift_path = `xcrun -f swift`.strip
160
- @platforms_path = File.join(@xcode_path, "Platforms")
161
- @platform_path = File.join(@platforms_path, "iPhoneSimulator.platform")
162
- @sdk_path = File.join(@platform_path, "Developer/SDKs/iPhoneSimulator.sdk")
156
+ @platforms_path = File.join(@xcode_path, 'Platforms')
157
+ @platform_path = File.join(@platforms_path, 'iPhoneSimulator.platform')
158
+ @sdk_path = File.join(@platform_path, 'Developer/SDKs/iPhoneSimulator.sdk')
163
159
  @testroot = nil
164
160
  @device_id = device_id
165
161
  @max_retry_count = max_retry_count
@@ -170,68 +166,109 @@ module XCKnife
170
166
  @naive_dump_bundle_names = naive_dump_bundle_names
171
167
  @skip_dump_bundle_names = skip_dump_bundle_names
172
168
  end
169
+ # rubocop:enable Metrics/ParameterLists
173
170
 
174
171
  def call(derived_data_folder, list_folder, extra_environment_variables = {})
175
172
  @testroot = File.join(derived_data_folder, 'Build', 'Products')
176
173
  xctestrun_file = Dir[File.join(@testroot, '*.xctestrun')].first
177
- if xctestrun_file.nil?
178
- raise ArgumentError, "No xctestrun on #{@testroot}"
179
- end
174
+ raise ArgumentError, "No xctestrun on #{@testroot}" if xctestrun_file.nil?
175
+
180
176
  xctestrun_as_json = `plutil -convert json -o - "#{xctestrun_file}"`
181
177
  FileUtils.mkdir_p(list_folder)
182
- list_tests(JSON.load(xctestrun_as_json), list_folder, extra_environment_variables)
178
+ list_tests(JSON.parse(xctestrun_as_json), list_folder, extra_environment_variables)
183
179
  end
184
180
 
185
181
  private
186
182
 
187
183
  attr_reader :testroot
188
184
 
189
- def list_tests(xctestrun, list_folder, extra_environment_variables)
190
- xctestrun.reject! { |test_bundle_name, _| test_bundle_name == '__xctestrun_metadata__' }
191
- xctestrun.map do |test_bundle_name, test_bundle|
185
+ def test_groups(xctestrun)
186
+ xctestrun.group_by do |test_bundle_name, _test_bundle|
192
187
  if @skip_dump_bundle_names.include?(test_bundle_name)
193
- logger.info { "Skipping dumping tests in `#{test_bundle_name}` -- writing out fake event"}
194
- test_specification = list_single_test(list_folder, test_bundle, test_bundle_name)
188
+ 'single'
195
189
  elsif @naive_dump_bundle_names.include?(test_bundle_name)
196
- test_specification = list_tests_with_nm(list_folder, test_bundle, test_bundle_name)
190
+ 'nm'
197
191
  else
198
- test_specification = list_tests_with_simctl(list_folder, test_bundle, test_bundle_name, extra_environment_variables)
199
- wait_test_dumper_completion(test_specification.json_stream_file)
192
+ 'simctl'
200
193
  end
194
+ end
195
+ end
196
+
197
+ # This executes naive test dumping in parallel by queueing up items onto a work queue to process
198
+ # with 1 new thread per processor. Results are placed onto a threadsafe spec queue to avoid writing
199
+ # to an object between threads, then popped off re-inserting them to our list of test results.
200
+ # rubocop:disable Metrics/CyclomaticComplexity
201
+ def list_tests(xctestrun, list_folder, extra_environment_variables)
202
+ xctestrun.reject! { |test_bundle_name, _| test_bundle_name == '__xctestrun_metadata__' }
203
+
204
+ test_runs_by_method = test_groups(xctestrun)
205
+ spec_queue = Queue.new
206
+ nm_bundle_queue = Queue.new
207
+ results = []
208
+ single_tests = test_runs_by_method['single'] || []
209
+ nm_tests = test_runs_by_method['nm'] || []
210
+ simctl_tests = test_runs_by_method['simctl'] || []
211
+
212
+ single_tests.each do |test_bundle_name, test_bundle|
213
+ logger.info { "Skipping dumping tests in `#{test_bundle_name}` -- writing out fake event" }
214
+ spec_queue << list_single_test(list_folder, test_bundle, test_bundle_name)
215
+ end
216
+
217
+ simctl_tests.each do |test_bundle_name, test_bundle|
218
+ test_spec = list_tests_with_simctl(list_folder, test_bundle, test_bundle_name, extra_environment_variables)
219
+ wait_test_dumper_completion(test_spec.json_stream_file)
201
220
 
202
- test_specification
221
+ spec_queue << test_spec
203
222
  end
223
+
224
+ nm_tests.each { |item| nm_bundle_queue << item }
225
+
226
+ [Etc.nprocessors, nm_bundle_queue.size].min.times.map do
227
+ nm_bundle_queue << :stop
228
+
229
+ Thread.new do
230
+ Thread.current.abort_on_exception = true
231
+
232
+ until (item = nm_bundle_queue.pop) == :stop
233
+ test_bundle_name, test_bundle = item
234
+ spec_queue << list_tests_with_nm(list_folder, test_bundle, test_bundle_name)
235
+ end
236
+ end
237
+ end.each(&:join)
238
+
239
+ results << spec_queue.pop until spec_queue.empty?
240
+
241
+ results
204
242
  end
243
+ # rubocop:enable Metrics/CyclomaticComplexity
205
244
 
206
245
  def list_tests_with_simctl(list_folder, test_bundle, test_bundle_name, extra_environment_variables)
207
- env_variables = test_bundle["EnvironmentVariables"]
208
- testing_env_variables = test_bundle["TestingEnvironmentVariables"]
246
+ env_variables = test_bundle['EnvironmentVariables']
247
+ testing_env_variables = test_bundle['TestingEnvironmentVariables']
209
248
  outpath = File.join(list_folder, test_bundle_name)
210
- test_host = replace_vars(test_bundle["TestHostPath"])
211
- test_bundle_path = replace_vars(test_bundle["TestBundlePath"], test_host)
249
+ test_host = replace_vars(test_bundle['TestHostPath'])
250
+ test_bundle_path = replace_vars(test_bundle['TestBundlePath'], test_host)
212
251
  test_dumper_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'TestDumper', 'TestDumper.dylib'))
213
- unless File.exist?(test_dumper_path)
214
- raise TestDumpError, "Could not find TestDumper.dylib on #{test_dumper_path}"
215
- end
252
+ raise TestDumpError, "Could not find TestDumper.dylib on #{test_dumper_path}" unless File.exist?(test_dumper_path)
216
253
 
217
- is_logic_test = test_bundle["TestHostBundleIdentifier"].nil?
254
+ is_logic_test = test_bundle['TestHostBundleIdentifier'].nil?
218
255
  env = simctl_child_attrs(
219
- "XCTEST_TYPE" => xctest_type(test_bundle),
220
- "XCTEST_TARGET" => test_bundle_name,
221
- "TestDumperOutputPath" => outpath,
222
- "IDE_INJECTION_PATH" => testing_env_variables["DYLD_INSERT_LIBRARIES"],
223
- "XCInjectBundleInto" => testing_env_variables["XCInjectBundleInto"],
224
- "XCInjectBundle" => test_bundle_path,
225
- "TestBundleLocation" => test_bundle_path,
226
- "OS_ACTIVITY_MODE" => "disable",
227
- "DYLD_PRINT_LIBRARIES" => "YES",
228
- "DYLD_PRINT_ENV" => "YES",
229
- "DYLD_ROOT_PATH" => @sdk_path,
230
- "DYLD_LIBRARY_PATH" => env_variables["DYLD_LIBRARY_PATH"],
231
- "DYLD_FRAMEWORK_PATH" => env_variables["DYLD_FRAMEWORK_PATH"],
232
- "DYLD_FALLBACK_LIBRARY_PATH" => "#{@sdk_path}/usr/lib",
233
- "DYLD_FALLBACK_FRAMEWORK_PATH" => "#{@platform_path}/Developer/Library/Frameworks",
234
- "DYLD_INSERT_LIBRARIES" => test_dumper_path,
256
+ 'XCTEST_TYPE' => xctest_type(test_bundle),
257
+ 'XCTEST_TARGET' => test_bundle_name,
258
+ 'TestDumperOutputPath' => outpath,
259
+ 'IDE_INJECTION_PATH' => testing_env_variables['DYLD_INSERT_LIBRARIES'],
260
+ 'XCInjectBundleInto' => testing_env_variables['XCInjectBundleInto'],
261
+ 'XCInjectBundle' => test_bundle_path,
262
+ 'TestBundleLocation' => test_bundle_path,
263
+ 'OS_ACTIVITY_MODE' => 'disable',
264
+ 'DYLD_PRINT_LIBRARIES' => 'YES',
265
+ 'DYLD_PRINT_ENV' => 'YES',
266
+ 'DYLD_ROOT_PATH' => @sdk_path,
267
+ 'DYLD_LIBRARY_PATH' => env_variables['DYLD_LIBRARY_PATH'],
268
+ 'DYLD_FRAMEWORK_PATH' => env_variables['DYLD_FRAMEWORK_PATH'],
269
+ 'DYLD_FALLBACK_LIBRARY_PATH' => "#{@sdk_path}/usr/lib",
270
+ 'DYLD_FALLBACK_FRAMEWORK_PATH' => "#{@platform_path}/Developer/Library/Frameworks",
271
+ 'DYLD_INSERT_LIBRARIES' => test_dumper_path
235
272
  )
236
273
  env.merge!(simctl_child_attrs(extra_environment_variables))
237
274
  inject_vars(env, test_host)
@@ -241,18 +278,21 @@ module XCKnife
241
278
  run_logic_test(env, test_host, test_bundle_path)
242
279
  else
243
280
  install_app(test_host)
244
- test_host_bundle_identifier = replace_vars(test_bundle["TestHostBundleIdentifier"], test_host)
281
+ test_host_bundle_identifier = replace_vars(test_bundle['TestHostBundleIdentifier'], test_host)
245
282
  run_apptest(env, test_host_bundle_identifier, test_bundle_path)
246
283
  end
247
- return TestSpecification.new outpath, discover_tests_to_skip(test_bundle)
284
+ TestSpecification.new outpath, discover_tests_to_skip(test_bundle)
248
285
  end
249
286
 
287
+ # Improvement?: assume that everything in the historical info is correct, so dont simctl or nm, and just spit out exactly what it said the classes were
288
+
250
289
  def list_tests_with_nm(list_folder, test_bundle, test_bundle_name)
251
290
  output_methods(list_folder, test_bundle, test_bundle_name) do |test_bundle_path|
252
291
  methods = []
253
292
  swift_demangled_nm(test_bundle_path) do |output|
254
293
  output.each_line do |line|
255
- next unless method = method_from_nm_line(line)
294
+ next unless (method = method_from_nm_line(line))
295
+
256
296
  methods << method
257
297
  end
258
298
  end
@@ -271,25 +311,25 @@ module XCKnife
271
311
  logger.info { "Writing out TestDumper file for #{test_bundle_name} to #{outpath}" }
272
312
  test_specification = TestSpecification.new outpath, discover_tests_to_skip(test_bundle)
273
313
 
274
- test_bundle_path = replace_vars(test_bundle["TestBundlePath"], replace_vars(test_bundle["TestHostPath"]))
314
+ test_bundle_path = replace_vars(test_bundle['TestBundlePath'], replace_vars(test_bundle['TestHostPath']))
275
315
  methods = yield(test_bundle_path)
276
316
 
277
317
  test_type = xctest_type(test_bundle)
278
318
  File.open test_specification.json_stream_file, 'a' do |f|
279
- f << JSON.dump(message: "Starting Test Dumper", event: "begin-test-suite", testType: test_type) << "\n"
319
+ f << JSON.dump(message: 'Starting Test Dumper', event: 'begin-test-suite', testType: test_type) << "\n"
280
320
  f << JSON.dump(event: 'begin-ocunit', bundleName: File.basename(test_bundle_path), targetName: test_bundle_name) << "\n"
281
321
  methods.map { |method| method[:class] }.uniq.each do |class_name|
282
- f << JSON.dump(test: '1', className: class_name, event: "end-test", totalDuration: "0") << "\n"
322
+ f << JSON.dump(test: '1', className: class_name, event: 'end-test', totalDuration: '0') << "\n"
283
323
  end
284
- f << JSON.dump(message: "Completed Test Dumper", event: "end-action", testType: test_type) << "\n"
324
+ f << JSON.dump(message: 'Completed Test Dumper', event: 'end-action', testType: test_type) << "\n"
285
325
  end
286
326
 
287
327
  test_specification
288
328
  end
289
329
 
290
330
  def discover_tests_to_skip(test_bundle)
291
- identifier_for_test_method = "/"
292
- skip_test_identifiers = test_bundle["SkipTestIdentifiers"] || []
331
+ identifier_for_test_method = '/'
332
+ skip_test_identifiers = test_bundle['SkipTestIdentifiers'] || []
293
333
  skip_test_identifiers.reject { |i| i.include?(identifier_for_test_method) }.to_set
294
334
  end
295
335
 
@@ -298,12 +338,11 @@ module XCKnife
298
338
  end
299
339
 
300
340
  def wrapped_simctl(args)
301
- args = [*gtimeout, simctl] + args
302
- args
341
+ [*gtimeout, simctl] + args
303
342
  end
304
343
 
305
344
  def gtimeout
306
- return [] unless @simctl_timeout > 0
345
+ return [] unless @simctl_timeout.positive?
307
346
 
308
347
  path = gtimeout_path
309
348
  if path.empty?
@@ -311,22 +350,22 @@ module XCKnife
311
350
  return []
312
351
  end
313
352
 
314
- [path, "-k", "5", "#{@simctl_timeout}"]
353
+ [path, '-k', '5', @simctl_timeout.to_s]
315
354
  end
316
355
 
317
356
  def gtimeout_path
318
357
  `which gtimeout`.strip
319
358
  end
320
359
 
321
- def replace_vars(str, testhost = "<UNKNOWN>")
322
- str.gsub("__PLATFORMS__", @platforms_path).
323
- gsub("__TESTHOST__", testhost).
324
- gsub("__TESTROOT__", testroot)
360
+ def replace_vars(str, testhost = '<UNKNOWN>')
361
+ str.gsub('__PLATFORMS__', @platforms_path)
362
+ .gsub('__TESTHOST__', testhost)
363
+ .gsub('__TESTROOT__', testroot)
325
364
  end
326
365
 
327
366
  def inject_vars(env, test_host)
328
367
  env.each do |k, v|
329
- env[k] = replace_vars(v || "", test_host)
368
+ env[k] = replace_vars(v || '', test_host)
330
369
  end
331
370
  end
332
371
 
@@ -339,47 +378,44 @@ module XCKnife
339
378
  def install_app(test_host_path)
340
379
  retries_count = 0
341
380
  max_retry_count = 3
342
- until (retries_count > max_retry_count) or call_simctl(["install", @device_id, test_host_path])
381
+ until (retries_count > max_retry_count) || call_simctl(['install', @device_id, test_host_path])
343
382
  retries_count += 1
344
383
  call_simctl ['shutdown', @device_id]
345
384
  call_simctl ['boot', @device_id]
346
385
  sleep 1.0
347
386
  end
348
387
 
349
- if retries_count > max_retry_count
350
- raise TestDumpError, "Installing #{test_host_path} failed"
351
- end
352
-
388
+ raise TestDumpError, "Installing #{test_host_path} failed" if retries_count > max_retry_count
353
389
  end
354
390
 
355
391
  def wait_test_dumper_completion(file)
356
392
  retries_count = 0
357
- until has_test_dumper_terminated?(file) do
393
+ until test_dumper_terminated?(file)
358
394
  retries_count += 1
359
- if retries_count == @max_retry_count
360
- raise TestDumpError, "Timeout error on: #{file}"
361
- end
395
+ raise TestDumpError, "Timeout error on: #{file}" if retries_count == @max_retry_count
396
+
362
397
  sleep 0.1
363
398
  end
364
399
  end
365
400
 
366
- def has_test_dumper_terminated?(file)
367
- return false unless File.exists?(file)
401
+ def test_dumper_terminated?(file)
402
+ return false unless File.exist?(file)
403
+
368
404
  last_line = `tail -n 1 "#{file}"`
369
- return last_line.include?("Completed Test Dumper")
405
+ last_line.include?('Completed Test Dumper')
370
406
  end
371
407
 
372
408
  def run_apptest(env, test_host_bundle_identifier, test_bundle_path)
373
- unless call_simctl(["launch", @device_id, test_host_bundle_identifier, '-XCTest', 'All', dylib_logfile_path, test_bundle_path], env: env)
374
- raise TestDumpError, "Launching #{test_bundle_path} in #{test_host_bundle_identifier} failed"
375
- end
409
+ return if call_simctl(['launch', @device_id, test_host_bundle_identifier, '-XCTest', 'All', dylib_logfile_path, test_bundle_path], env: env)
410
+
411
+ raise TestDumpError, "Launching #{test_bundle_path} in #{test_host_bundle_identifier} failed"
376
412
  end
377
413
 
378
414
  def run_logic_test(env, test_host, test_bundle_path)
379
- opts = @debug ? {} : { err: "/dev/null" }
380
- unless call_simctl(["spawn", @device_id, test_host, '-XCTest', 'All', dylib_logfile_path, test_bundle_path], env: env, **opts)
381
- raise TestDumpError, "Spawning #{test_bundle_path} in #{test_host} failed"
382
- end
415
+ opts = @debug ? {} : { err: '/dev/null' }
416
+ return if call_simctl(['spawn', @device_id, test_host, '-XCTest', 'All', dylib_logfile_path, test_bundle_path], env: env, **opts)
417
+
418
+ raise TestDumpError, "Spawning #{test_bundle_path} in #{test_host} failed"
383
419
  end
384
420
 
385
421
  def call_simctl(args, env: {}, **spawn_opts)
@@ -398,21 +434,19 @@ module XCKnife
398
434
  end
399
435
 
400
436
  def xctest_type(test_bundle)
401
- if test_bundle["TestHostBundleIdentifier"].nil?
402
- "LOGICTEST"
437
+ if test_bundle['TestHostBundleIdentifier'].nil?
438
+ 'LOGICTEST'
403
439
  else
404
- "APPTEST"
440
+ 'APPTEST'
405
441
  end
406
442
  end
407
443
 
408
- def swift_demangled_nm(test_bundle_path)
409
- Open3.pipeline_r([@nm_path, File.join(test_bundle_path, File.basename(test_bundle_path, '.xctest'))], [@swift_path, 'demangle']) do |o, _ts|
410
- yield(o)
411
- end
444
+ def swift_demangled_nm(test_bundle_path, &block)
445
+ Open3.pipeline_r([@nm_path, File.join(test_bundle_path, File.basename(test_bundle_path, '.xctest'))], [@swift_path, 'demangle'], &block)
412
446
  end
413
447
 
414
448
  def method_from_nm_line(line)
415
- return unless line.strip =~ %r{^
449
+ return unless line.strip =~ /^
416
450
  [\da-f]+\s # address
417
451
  [tT]\s # symbol type
418
452
  (?: # method
@@ -420,13 +454,13 @@ module XCKnife
420
454
  | # or swift instance method
421
455
  _? # only present on Xcode 10.0 and below
422
456
  (?:@objc\s)? # optional objc annotation
423
- (?:[^\.]+\.)? # module name
457
+ (?:[^.]+\.)? # module name
424
458
  (.+) # class name
425
459
  \.(test.+)\s->\s\(\) # method signature
426
460
  )
427
- $}ox
461
+ $/ox
428
462
 
429
- { class: $1 || $3, method: $2 || $4 }
463
+ { class: Regexp.last_match(1) || Regexp.last_match(3), method: Regexp.last_match(2) || Regexp.last_match(4) }
430
464
  end
431
465
  end
432
466
  end