class Snapshot::SimulatorLauncher

Public Instance Methods

cleanup_after_failure(devices, language, locale, launch_args, return_code) click to toggle source
# File snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb, line 141
def cleanup_after_failure(devices, language, locale, launch_args, return_code)
  copy_screenshots(language: language, locale: locale, launch_args: launch_args)

  UI.important("Tests failed while running on: #{devices.join(', ')}")
  UI.important("For more detail about the test failures, check the logs here:")
  UI.important(xcodebuild_log_path(language: language, locale: locale))
  UI.important(" ")
  UI.important("You can also find the test result data here:")
  UI.important(test_results_path)
  UI.important(" ")
  UI.important("You can find the incomplete screenshots here:")
  UI.important(SCREENSHOTS_DIR)
  UI.important(launcher_config.output_directory)
end
copy_screenshots(language: nil, locale: nil, launch_args: nil) click to toggle source
# File snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb, line 171
def copy_screenshots(language: nil, locale: nil, launch_args: nil)
  raw_output = File.read(xcodebuild_log_path(language: language, locale: locale))
  dir_name = locale || language
  return Collector.fetch_screenshots(raw_output, dir_name, '', launch_args.first)
end
default_number_of_simultaneous_simulators() click to toggle source

With Xcode 9's ability to run tests on multiple concurrent simulators, this method sets the maximum number of simulators to run simultaneously to avoid overloading your machine.

# File snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb, line 34
def default_number_of_simultaneous_simulators
  cpu_count = CPUInspector.cpu_count
  if cpu_count <= 2
    return cpu_count
  end

  return cpu_count - 1
end
execute(retries = 0, command: nil, language: nil, locale: nil, launch_args: nil, devices: nil) click to toggle source
# File snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb, line 103
def execute(retries = 0, command: nil, language: nil, locale: nil, launch_args: nil, devices: nil)
  prefix_hash = [
    {
      prefix: "Running Tests: ",
      block: proc do |value|
        value.include?("Touching")
      end
    }
  ]
  FastlaneCore::CommandExecutor.execute(command: command,
                                      print_all: true,
                                  print_command: true,
                                         prefix: prefix_hash,
                                        loading: "Loading...",
                                          error: proc do |output, return_code|
                                            self.collected_errors.concat(failed_devices.map do |device, messages|
                                              "#{device}: #{messages.join(', ')}"
                                            end)

                                            cleanup_after_failure(devices, language, locale, launch_args, return_code)

                                            # no exception raised... that means we need to retry
                                            UI.error("Caught error... #{return_code}")

                                            self.current_number_of_retries_due_to_failing_simulator += 1
                                            if self.current_number_of_retries_due_to_failing_simulator < 20 && return_code != 65
                                              # If the return code is not 65, we should assume its a simulator failure and retry
                                              launch_simultaneously(devices, language, locale, launch_args)
                                            elsif retries < launcher_config.number_of_retries
                                              # If there are retries remaining, run the tests again
                                              retry_tests(retries, command, language, locale, launch_args, devices)
                                            else
                                              # It's important to raise an error, as we don't want to collect the screenshots
                                              UI.crash!("Too many errors... no more retries...") if launcher_config.stop_after_first_error
                                            end
                                          end)
end
failed_devices() click to toggle source

This method returns a hash of { device name => [failure messages] } {

'iPhone 7': [], # this empty array indicates success
'iPhone 7 Plus': ["No tests were executed"],
'iPad Air': ["Launch session expired", "Array out of bounds"]

}

# File snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb, line 188
def failed_devices
  test_summaries = Dir["#{test_results_path}/*_TestSummaries.plist"]
  test_summaries.each_with_object({}) do |plist, hash|
    summary = FastlaneCore::TestParser.new(plist)
    name = summary.data.first[:run_destination_name]
    if summary.data.first[:number_of_tests] == 0
      hash[name] = ["No tests were executed"]
    else
      tests = Array(summary.data.first[:tests])
      hash[name] = tests.flat_map { |test| Array(test[:failures]).map { |failure| failure[:failure_message] } }
    end
  end
end
launch_simultaneously(devices, language, locale, launch_arguments) click to toggle source
# File snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb, line 79
def launch_simultaneously(devices, language, locale, launch_arguments)
  prepare_for_launch(devices, language, locale, launch_arguments)

  add_media(devices, :photo, launcher_config.add_photos) if launcher_config.add_photos
  add_media(devices, :video, launcher_config.add_videos) if launcher_config.add_videos

  command = TestCommandGenerator.generate(
    devices: devices,
    language: language,
    locale: locale,
    log_path: xcodebuild_log_path(language: language, locale: locale)
  )

  devices.each { |device_type| override_status_bar(device_type, launcher_config.override_status_bar_arguments) } if launcher_config.override_status_bar

  UI.important("Running snapshot on: #{devices.join(', ')}")

  execute(command: command, language: language, locale: locale, launch_args: launch_arguments, devices: devices)

  devices.each { |device_type| clear_status_bar(device_type) } if launcher_config.override_status_bar

  return copy_screenshots(language: language, locale: locale, launch_args: launch_arguments)
end
retry_tests(retries, command, language, locale, launch_args, devices) click to toggle source
# File snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb, line 156
def retry_tests(retries, command, language, locale, launch_args, devices)
  UI.important("Retrying on devices: #{devices.join(', ')}")
  UI.important("Number of retries remaining: #{launcher_config.number_of_retries - retries - 1}")

  # Make sure all needed directories exist for next retry
  # `copy_screenshots` in `cleanup_after_failure` can delete some needed directories
  # https://github.com/fastlane/fastlane/issues/10786
  prepare_directories_for_launch(language: language, locale: locale, launch_arguments: launch_args)

  # Clear errors so a successful retry isn't reported as an over failure
  self.collected_errors = []

  execute(retries + 1, command: command, language: language, locale: locale, launch_args: launch_args, devices: devices)
end
take_screenshots_simultaneously() click to toggle source
# File snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb, line 43
def take_screenshots_simultaneously
  languages_finished = {}
  launcher_config.launch_args_set.each do |launch_args|
    launcher_config.languages.each_with_index do |language, language_index|
      locale = nil
      if language.kind_of?(Array)
        locale = language[1]
        language = language[0]
      end

      # Clear logs so subsequent xcodebuild executions don't append to old ones
      log_path = xcodebuild_log_path(language: language, locale: locale)
      File.delete(log_path) if File.exist?(log_path)

      # Break up the array of devices into chunks that can
      # be run simultaneously.
      if launcher_config.concurrent_simulators?
        all_devices = launcher_config.devices
        # We have to break up the concurrent simulators by device version too, otherwise there is an error (see #10969)
        by_simulator_version = all_devices.group_by { |d| FastlaneCore::DeviceManager.latest_simulator_version_for_device(d) }.values
        device_batches = by_simulator_version.flat_map { |a| a.each_slice(default_number_of_simultaneous_simulators).to_a }
      else
        # Put each device in it's own array to run tests one at a time
        device_batches = launcher_config.devices.map { |d| [d] }
      end

      device_batches.each do |devices|
        languages_finished[language] = launch_simultaneously(devices, language, locale, launch_args)
      end
    end
  end
  launcher_config.devices.each_with_object({}) do |device, results_hash|
    results_hash[device] = languages_finished
  end
end
test_results_path() click to toggle source
# File snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb, line 177
def test_results_path
  derived_data_path = TestCommandGenerator.derived_data_path
  return File.join(derived_data_path, 'Logs/Test')
end
xcodebuild_log_path(language: nil, locale: nil) click to toggle source
# File snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb, line 202
def xcodebuild_log_path(language: nil, locale: nil)
  name_components = [Snapshot.project.app_name, Snapshot.config[:scheme]]

  if Snapshot.config[:namespace_log_files]
    name_components << launcher_config.devices.join('-') if launcher_config.devices.count >= 1
    name_components << language if language
    name_components << locale if locale
  end

  file_name = "#{name_components.join('-')}.log"

  containing = File.expand_path(Snapshot.config[:buildlog_path])
  FileUtils.mkdir_p(containing)

  return File.join(containing, file_name)
end