Class: Snapshot::SimulatorLauncher

Inherits:
SimulatorLauncherBase show all
Defined in:
snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb

Instance Attribute Summary

Attributes inherited from SimulatorLauncherBase

#collected_errors, #current_number_of_retries_due_to_failing_simulator, #launcher_config

Instance Method Summary collapse

Methods inherited from SimulatorLauncherBase

#add_media, #copy_simulator_logs, #erase_simulator, #initialize, #localize_simulator, #prepare_directories_for_launch, #prepare_for_launch, #prepare_simulators_for_launch, #uninstall_app

Constructor Details

This class inherits a constructor from Snapshot::SimulatorLauncherBase

Instance Method Details

#cleanup_after_failure(devices, language, locale, launch_args, return_code) ⇒ Object


137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb', line 137

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) ⇒ Object


167
168
169
170
171
# File 'snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb', line 167

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_simulatorsObject

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.


34
35
36
37
38
39
40
41
# 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) ⇒ Object


99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb', line 99

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_devicesObject

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"]


184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb', line 184

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.map { |test| Array(test[:failures]).map { |failure| failure[:failure_message] } }.flatten
    end
  end
end

#launch_simultaneously(devices, language, locale, launch_arguments) ⇒ Object


79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# 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)
  )

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

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

  return copy_screenshots(language: language, locale: locale, launch_args: launch_arguments)
end

#retry_tests(retries, command, language, locale, launch_args, devices) ⇒ Object


152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb', line 152

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_simultaneouslyObject


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# 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 dont 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_pathObject


173
174
175
176
# File 'snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb', line 173

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) ⇒ Object


198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb', line 198

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