class TestCenter::Helper::MultiScanManager::Runner

Attributes

retry_total_count[R]

Public Class Methods

new(multi_scan_options) click to toggle source
# 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

Public Instance Methods

collate_batched_reports() click to toggle source
# 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) click to toggle source
# 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_junits() click to toggle source
# 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}/*"

  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) click to toggle source
# 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_location() click to toggle source
# 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 = []) click to toggle source
# 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_bundles() click to toggle source
# 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_bundles() click to toggle source
# 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_tests() click to toggle source
# 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
run() click to toggle source
# 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_batches() click to toggle source
# 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_try() click to toggle source
# 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) click to toggle source
# 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_logcollection() click to toggle source
# 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_device() { || ... } click to toggle source
# 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_testcollector() click to toggle source
# 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?() click to toggle source
# 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
update_options_to_use_xcresult_output() click to toggle source
# 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