Class: TestCenter::Helper::MultiScanManager::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(multi_scan_options) ⇒ Runner

Returns a new instance of Runner.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 14

def initialize(multi_scan_options)
  @options = multi_scan_options.merge(
    clean: false,
    disable_concurrent_testing: true
  )
  @result_bundle_desired = !!@options[:result_bundle]
  if @options[:result_bundle] && FastlaneCore::Helper.xcode_at_least?('11.0.0')
    update_options_to_use_xcresult_output
  end
  @batch_count = 1 # default count. Will be updated by setup_testcollector
  @options[:parallel_testrun_count] ||= 1
  @initial_parallel_testrun_count = @options[:parallel_testrun_count]
  setup_testcollector
  setup_logcollection
  FastlaneCore::UI.verbose("< done in TestCenter::Helper::MultiScanManager.initialize")
end

Instance Attribute Details

#retry_total_countObject (readonly)

Returns the value of attribute retry_total_count.



12
13
14
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 12

def retry_total_count
  @retry_total_count
end

Instance Method Details

#collate_batched_reportsObject



276
277
278
279
280
281
282
283
284
285
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 276

def collate_batched_reports
  return unless @batch_count > 1
  return unless @options[:collate_reports]

  @test_collector.testables.each do |testable|
    collate_batched_reports_for_testable(testable)
  end
  collate_multitarget_junits
  move_single_testable_reports_to_final_location
end

#collate_batched_reports_for_testable(testable) ⇒ Object



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 370

def collate_batched_reports_for_testable(testable)
  FastlaneCore::UI.verbose("Collating results for all batches")

  absolute_output_directory = File.join(
    File.absolute_path(output_directory),
    testable
  )
  source_reports_directory_glob = "#{absolute_output_directory}-batch-*"

  given_custom_report_file_name = @options[:custom_report_file_name]
  given_output_types = @options[:output_types]
  given_output_files = @options[:output_files]

  report_name_helper = ReportNameHelper.new(
    given_output_types,
    given_output_files,
    given_custom_report_file_name
  )

  TestCenter::Helper::MultiScanManager::ReportCollator.new(
    source_reports_directory_glob: source_reports_directory_glob,
    output_directory: absolute_output_directory,
    reportnamer: report_name_helper,
    scheme: @options[:scheme],
    result_bundle: @options[:result_bundle]
  ).collate
  logs_glog_pattern = "#{source_reports_directory_glob}/*system_logs-*.{log,logarchive}"
  logs = Dir.glob(logs_glog_pattern)
  FileUtils.mv(logs, absolute_output_directory, force: true)
  FileUtils.rm_rf(Dir.glob(source_reports_directory_glob))
  symlink_result_bundle_to_xcresult(absolute_output_directory, report_name_helper)
  true
end

#collate_multitarget_junitsObject



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 345

def collate_multitarget_junits
  return if @test_collector.testables.size < 2

  Fastlane::UI.verbose("Collating test targets's junit results")

  given_custom_report_file_name = @options[:custom_report_file_name]
  given_output_types = @options[:output_types]
  given_output_files = @options[:output_files]

  report_name_helper = ReportNameHelper.new(
    given_output_types,
    given_output_files,
    given_custom_report_file_name
  )

  absolute_output_directory = File.absolute_path(output_directory)
  source_reports_directory_glob = "#{absolute_output_directory}/*"
  FastlaneCore::UI.verbose("MultiScanManager::Runner sending 'source_reports_directory_glob' of \"#{source_reports_directory_glob}\"")
  TestCenter::Helper::MultiScanManager::ReportCollator.new(
    source_reports_directory_glob: source_reports_directory_glob,
    output_directory: absolute_output_directory,
    reportnamer: report_name_helper
  ).collate_junit_reports
end

#merge_single_testable_xcresult_with_final_xcresult(testable_output_dir, final_output_dir) ⇒ Object



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 299

def merge_single_testable_xcresult_with_final_xcresult(testable_output_dir, final_output_dir)
  reportnamer = ReportNameHelper.new(
    @options[:output_types],
    @options[:output_files],
    @options[:custom_report_file_name]
  )
  return unless reportnamer.includes_xcresult?

  xcresult_bundlename = reportnamer.xcresult_bundlename
  src_xcresult_bundlepath = File.join(testable_output_dir, xcresult_bundlename)
  dst_xcresult_bundlepath = File.join(final_output_dir, xcresult_bundlename)

  # if there is no destination bundle to merge to, skip it as any source bundle will be copied when complete.
  return if !File.exist?(dst_xcresult_bundlepath)
  # if there is no source bundle to merge, skip it as there is nothing to merge.
  return if !File.exist?(src_xcresult_bundlepath)

  config = FastlaneCore::Configuration.create(
    Fastlane::Actions::CollateXcresultsAction.available_options,
    {
      xcresults: [src_xcresult_bundlepath, dst_xcresult_bundlepath],
      collated_xcresult: dst_xcresult_bundlepath
    }
  )
  FastlaneCore::UI.verbose("Merging xcresult '#{src_xcresult_bundlepath}' to '#{dst_xcresult_bundlepath}'")
  Fastlane::Actions::CollateXcresultsAction.run(config)
  FileUtils.rm_rf(src_xcresult_bundlepath)
  if @result_bundle_desired
    xcresult_bundlename = reportnamer.xcresult_bundlename
    test_result_bundlename = File.basename(xcresult_bundlename, '.*') + '.test_result'
    test_result_bundlename_path = File.join(testable_output_dir, test_result_bundlename)
    FileUtils.rm_rf(test_result_bundlename_path)
  end
end

#move_single_testable_reports_to_final_locationObject



287
288
289
290
291
292
293
294
295
296
297
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 287

def move_single_testable_reports_to_final_location
  return unless @test_collector.testables.size == 1

  report_files_dir = File.join(
    File.absolute_path(output_directory),
    @test_collector.testables.first
  )
  merge_single_testable_xcresult_with_final_xcresult(report_files_dir, File.absolute_path(output_directory))
  FileUtils.cp_r("#{report_files_dir}/.", File.absolute_path(output_directory))
  FileUtils.rm_rf(report_files_dir)
end

#output_directory(batch_index = 0, test_batch = []) ⇒ Object



80
81
82
83
84
85
86
87
88
89
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 80

def output_directory(batch_index = 0, test_batch = [])
  undecorated_output_directory = File.absolute_path(@options.fetch(:output_directory, 'test_results'))

  return undecorated_output_directory if batch_index.zero?

  absolute_output_directory = undecorated_output_directory

  testable = test_batch.first.split('/').first || ''
  File.join(absolute_output_directory, "#{testable}-batch-#{batch_index}")
end

#remote_preexisting_xcresult_bundlesObject



162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 162

def remote_preexisting_xcresult_bundles
  return unless @options.fetch(:output_types, '').include?('xcresult')

  glob_pattern = "#{output_directory}/**/*.xcresult"
  preexisting_xcresult_bundles = Dir.glob(glob_pattern)
  if preexisting_xcresult_bundles.size > 0
    FastlaneCore::UI.verbose("Removing pre-existing xcresult bundles: ")
    preexisting_xcresult_bundles.each do |test_result_bundle|
      FastlaneCore::UI.verbose("  #{test_result_bundle}")
    end
    FileUtils.rm_rf(preexisting_xcresult_bundles)
  end
end

#remove_preexisting_test_result_bundlesObject



148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 148

def remove_preexisting_test_result_bundles
  return unless @options[:result_bundle] || @options[:output_types]&.include?('xcresult')

  glob_pattern = "#{output_directory}/**/*.test_result"
  preexisting_test_result_bundles = Dir.glob(glob_pattern)
  if preexisting_test_result_bundles.size > 0
    FastlaneCore::UI.verbose("Removing pre-existing test_result bundles: ")
    preexisting_test_result_bundles.each do |test_result_bundle|
      FastlaneCore::UI.verbose("  #{test_result_bundle}")
    end
    FileUtils.rm_rf(preexisting_test_result_bundles)
  end
end

#retrieve_failed_single_try_testsObject



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 215

def retrieve_failed_single_try_tests
  reportnamer = ReportNameHelper.new(
    @options[:output_types],
    @options[:output_files],
    @options[:custom_report_file_name]
  )
  report_filepath = File.join(output_directory, reportnamer.junit_last_reportname)
  config = FastlaneCore::Configuration.create(
    Fastlane::Actions::TestsFromJunitAction.available_options,
    {
      junit: File.absolute_path(report_filepath)
    }
  )
  Fastlane::Actions::TestsFromJunitAction.run(config)[:failed]
end

#runObject



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 91

def run
  ScanHelper.remove_preexisting_simulator_logs(@options)
  remove_preexisting_test_result_bundles
  remote_preexisting_xcresult_bundles

  test_results = [false]
  if should_run_tests_through_single_try?
    test_results.clear
    setup_run_tests_for_each_device do |device_name|
      FastlaneCore::UI.message("Single try testing for device '#{device_name}'") if device_name
      test_results << run_tests_through_single_try
    end
  end

  unless test_results.all? || @options[:try_count] < 1
    test_results.clear
    setup_testcollector
    setup_run_tests_for_each_device do |device_name|
      FastlaneCore::UI.message("Testing batches for device '#{device_name}'") if device_name
      test_results << run_test_batches
    end
  end
  test_results.all?
end

#run_test_batchesObject



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 231

def run_test_batches
  test_batch_results = []
  pool_options = @options.reject { |key| %i[device devices force_quit_simulator].include?(key) }
  pool_options[:test_batch_results] = test_batch_results
  pool_options[:xctestrun] = @test_collector.xctestrun_path

  serial_test_batches = (@options.fetch(:parallel_testrun_count, 1) == 1)
  if serial_test_batches && !@options[:invocation_based_tests]
    SimulatorHelper.call_simulator_started_callback(@options, Scan.devices)
  end

  pool = TestBatchWorkerPool.new(pool_options)
  pool.setup_workers

  remaining_test_batches = @test_collector.batches.clone
  remaining_test_batches.each_with_index do |test_batch, current_batch_index|
    worker = pool.wait_for_worker
    FastlaneCore::UI.message("Starting test run #{current_batch_index + 1}")
    worker.run(scan_options_for_worker(test_batch, current_batch_index))
  end
  pool.wait_for_all_workers
  collate_batched_reports
  FastlaneCore::UI.verbose("Results for each test run: #{test_batch_results}")
  test_batch_results.all?
end

#run_tests_through_single_tryObject



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 176

def run_tests_through_single_try
  FastlaneCore::UI.verbose("Running invocation tests")
  if @options[:invocation_based_tests]
    @options[:skip_testing] = @options[:skip_testing]&.map(&:strip_testcase)&.uniq
  end
  @options[:output_directory] = output_directory
  @options[:destination] = Scan.config[:destination]

  # We do not want Scan.config to _not_ have :device :devices, we want to
  # use :destination. We remove :force_quit_simulator as we do not want
  # Scan to handle it as multi_scan takes care of it in its own way
  options = @options.reject { |key| %i[device devices force_quit_simulator].include?(key) }
  options[:try_count] = 1

  SimulatorHelper.call_simulator_started_callback(@options, Scan.devices)

  tests_passed = RetryingScan.run(options)
  @options[:try_count] -= 1

  reportnamer = ReportNameHelper.new(
    @options[:output_types],
    @options[:output_files],
    @options[:custom_report_file_name]
  )
  report_filepath = File.join(output_directory, reportnamer.junit_last_reportname)
  config = FastlaneCore::Configuration.create(
    Fastlane::Actions::TestsFromJunitAction.available_options,
    {
      junit: File.absolute_path(report_filepath)
    }
  )
  @options[:only_testing] = retrieve_failed_single_try_tests
  @options[:only_testing] = @options[:only_testing].map(&:strip_testcase).uniq

  symlink_result_bundle_to_xcresult(output_directory, reportnamer)

  tests_passed
end

#scan_options_for_worker(test_batch, batch_index) ⇒ Object



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 257

def scan_options_for_worker(test_batch, batch_index)
  if @test_collector.batches.size > 1
    # If there are more than 1 batch, then we want each batch result
    # sent to a "batch index" output folder to be collated later
    # into the requested output_folder.
    # Otherwise, send the results from the one and only one batch
    # to the requested output_folder
    batch_index += 1
    batch = batch_index
  end

  {
    only_testing: test_batch.map(&:shellsafe_testidentifier),
    output_directory: output_directory(batch_index, test_batch),
    try_count: @options[:try_count],
    batch: batch
  }
end

#setup_logcollectionObject



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 43

def setup_logcollection
  FastlaneCore::UI.verbose("> setup_logcollection")
  return unless @options[:include_simulator_logs]
  return unless @options[:platform] == :ios_simulator
  return if Scan::Runner.method_defined?(:prelaunch_simulators)

  # We need to prelaunch the simulators so xcodebuild
  # doesn't shut it down before we have a chance to get
  # the logs.
  FastlaneCore::UI.verbose("\t collecting devices to boot for log collection")
  devices_to_shutdown = []
  Scan.devices.each do |device|
    devices_to_shutdown << device if device.state == "Shutdown"
    device.boot
  end
  at_exit do
    devices_to_shutdown.each(&:shutdown)
  end
  FastlaneCore::UI.verbose("\t fixing FastlaneCore::Simulator.copy_logarchive")
  FastlaneCore::Simulator.send(:include, FixedCopyLogarchiveFastlaneSimulator)
end

#setup_run_tests_for_each_deviceObject



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 116

def setup_run_tests_for_each_device
  original_output_directory = @options.fetch(:output_directory, 'test_results')
  unless @options[:platform] == :ios_simulator
    yield
    return
  end

  scan_destinations = Scan.config[:destination].clone
  try_count = @options[:try_count]

  scan_destinations.each_with_index do |destination, device_index|
    @options[:try_count] = try_count
    device_udid_match = destination.match(/id=(?<udid>[^,]+)/)
    device_udid = device_udid_match[:udid] if device_udid_match
    if scan_destinations.size > 1
      @options[:output_directory] = File.join(original_output_directory, device_udid)
      Scan.config[:destination].replace([destination])
    end
    command = "xcrun simctl list devices | grep #{device_udid}"
    device_info = Fastlane::Actions.sh(command, log: false)

    yield device_info.strip.gsub(/ \(#{device_udid}.*/, '')
  end
  Scan.config[:destination].replace(scan_destinations)
  @options[:output_directory] = original_output_directory
end

#setup_testcollectorObject



65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 65

def setup_testcollector
  return if @options[:invocation_based_tests] && @options[:only_testing].nil?
  return if @test_collector

  @test_collector = TestCollector.new(@options)
  @options.reject! { |key| %i[testplan].include?(key) }
  @batch_count = @test_collector.batches.size
  @options[:parallel_testrun_count] = @initial_parallel_testrun_count
  tests = @test_collector.batches.flatten
  if tests.size < @options[:parallel_testrun_count].to_i
    FastlaneCore::UI.important(":parallel_testrun_count greater than the number of tests (#{tests.size}). Reducing to that number.")
    @options[:parallel_testrun_count] = tests.size
  end
end

#should_run_tests_through_single_try?Boolean

Returns:

  • (Boolean)


143
144
145
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 143

def should_run_tests_through_single_try?
  @options[:invocation_based_tests] && @options[:only_testing].nil?
end


334
335
336
337
338
339
340
341
342
343
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 334

def symlink_result_bundle_to_xcresult(output_dir, reportname_helper)
  return unless @result_bundle_desired && reportname_helper.includes_xcresult?

  xcresult_bundlename = reportname_helper.xcresult_bundlename
  xcresult_bundlename_path = File.join(output_dir, xcresult_bundlename)
  test_result_bundlename = File.basename(xcresult_bundlename, '.*') + '.test_result'
  test_result_bundlename_path = File.join(output_dir, test_result_bundlename)
  FileUtils.rm_rf(test_result_bundlename_path)
  File.symlink(xcresult_bundlename_path, test_result_bundlename_path)
end

#update_options_to_use_xcresult_outputObject



31
32
33
34
35
36
37
38
39
40
41
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb', line 31

def update_options_to_use_xcresult_output
  return @options unless @options[:result_bundle]

  updated_output_types, updated_output_files = ReportNameHelper.ensure_output_includes_xcresult(
    @options[:output_types],
    @options[:output_files]
  )
  @options[:output_types] = updated_output_types
  @options[:output_files] = updated_output_files
  @options.reject! { |k,_| k == :result_bundle }
end