Class: TestCenter::Helper::MultiScanManager::RetryingScanHelper

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ RetryingScanHelper

Returns a new instance of RetryingScanHelper.

Raises:

  • (ArgumentError)


8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 8

def initialize(options)
  raise ArgumentError, 'Do not use the :device or :devices option. Instead use the :destination option.' if (options.key?(:device) or options.key?(:devices))

  @options = options
  @testrun_count = 0
  @xcpretty_json_file_output = ENV['XCPRETTY_JSON_FILE_OUTPUT']
  @reportnamer = ReportNameHelper.new(
    @options[:output_types],
    @options[:output_files],
    @options[:custom_report_file_name]
  )
end

Instance Attribute Details

#testrun_countObject (readonly)

Returns the value of attribute testrun_count.



6
7
8
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 6

def testrun_count
  @testrun_count
end

Instance Method Details

#after_testrun(exception = nil) ⇒ Object

after_testrun methods



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
142
143
144
145
146
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 117

def after_testrun(exception = nil)
  move_simulator_logs_for_next_run

  @testrun_count = @testrun_count + 1
  FastlaneCore::UI.verbose("Batch ##{@options[:batch]} incrementing retry count to #{@testrun_count}")
  if exception.kind_of?(FastlaneCore::Interface::FastlaneTestFailure)
    after_testrun_message = "Scan found failing tests"
    after_testrun_message << " for batch ##{@options[:batch]}" unless @options[:batch].nil?
    FastlaneCore::UI.verbose(after_testrun_message)

    handle_test_failure
  elsif exception.kind_of?(FastlaneCore::Interface::FastlaneBuildFailure)
    after_testrun_message = "Scan unable to test"
    after_testrun_message << " for batch ##{@options[:batch]}" unless @options[:batch].nil?
    FastlaneCore::UI.verbose(after_testrun_message)

    if @options[:retry_test_runner_failures]
      continue_with_build_failure(exception)
    else
      handle_build_failure(exception)
    end
  else
    after_testrun_message = "Scan passed the tests"
    after_testrun_message << " for batch ##{@options[:batch]}" unless @options[:batch].nil?
    FastlaneCore::UI.verbose(after_testrun_message)

    handle_success
  end
  collate_reports
end

#before_testrunObject



21
22
23
24
25
26
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 21

def before_testrun
  delete_xcresults # has to be performed _after_ moving a *.test_result
  quit_simulator
  set_json_env
  print_starting_scan_message
end

#collate_reportsObject



154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 154

def collate_reports
  return unless @options[:collate_reports]

  report_collator_options = {
    source_reports_directory_glob: output_directory,
    output_directory: output_directory,
    reportnamer: @reportnamer,
    scheme: @options[:scheme],
    result_bundle: @options[:result_bundle]
  }
  TestCenter::Helper::MultiScanManager::ReportCollator.new(report_collator_options).collate
end

#continue_with_build_failure(exception) ⇒ Object



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

def continue_with_build_failure(exception)
  test_session_last_messages = last_lines_of_test_session_log
  failure = retrieve_test_operation_failure(test_session_last_messages)
  case failure
  when /Lost connection to testmanagerd/
    FastlaneCore::UI.important("com.apple.CoreSimulator.CoreSimulatorService may have become corrupt, consider quitting it")
    if @options[:quit_core_simulator_service]
      Fastlane::Actions::RestartCoreSimulatorServiceAction.run
    end
  else
    FastlaneCore::UI.important(test_session_last_messages)
  end
  send_callback_testrun_info(test_operation_failure: failure)
end

#delete_xcresultsObject



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 40

def delete_xcresults
  if @reportnamer.includes_xcresult?
    FileUtils.rm_rf(File.join(output_directory, @reportnamer.xcresult_last_bundlename))
    return
  end

  derived_data_path = File.expand_path(@options[:derived_data_path] || Scan.config[:derived_data_path])
  xcresults = Dir.glob("#{derived_data_path}/Logs/Test/*.xcresult")
  if FastlaneCore::Helper.xcode_at_least?('11')
    xcresults += Dir.glob("#{output_directory}/*.xcresult")
  end
  FastlaneCore::UI.verbose("Deleting xcresults:")
  xcresults.each do |xcresult|
    FastlaneCore::UI.verbose("  #{xcresult}")
  end
  FileUtils.rm_rf(xcresults)
end

#failure_details(additional_info) ⇒ Object



195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 195

def failure_details(additional_info)
  return [{}, nil] if additional_info.key?(:test_operation_failure)

  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)
    }
  )
  junit_results = Fastlane::Actions::TestsFromJunitAction.run(config)

  [junit_results, report_filepath]
end

#handle_build_failure(exception) ⇒ Object



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 273

def handle_build_failure(exception)
  test_session_last_messages = last_lines_of_test_session_log
  failure = retrieve_test_operation_failure(test_session_last_messages)
  case failure
  when /Test runner exited before starting test execution/
    FastlaneCore::UI.error(failure)
  when /Lost connection to testmanagerd/
    FastlaneCore::UI.error(failure)
    FastlaneCore::UI.important("com.apple.CoreSimulator.CoreSimulatorService may have become corrupt, consider quitting it")
    if @options[:quit_core_simulator_service]
      Fastlane::Actions::RestartCoreSimulatorServiceAction.run
    end
  else
    FastlaneCore::UI.error(test_session_last_messages)
    send_callback_testrun_info(test_operation_failure: failure)
    raise exception
    FastlaneCore::UI.important(test_session_last_messages)
  end
  send_callback_testrun_info(test_operation_failure: failure)
end

#handle_successObject



148
149
150
151
152
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 148

def handle_success
  send_callback_testrun_info
  move_test_result_bundle_for_next_run
  reset_json_env
end

#handle_test_failureObject



167
168
169
170
171
172
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 167

def handle_test_failure
  send_callback_testrun_info
  move_test_result_bundle_for_next_run
  update_scan_options
  @reportnamer.increment
end

#last_lines_of_test_session_logObject



323
324
325
326
327
328
329
330
331
332
333
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 323

def last_lines_of_test_session_log
  derived_data_path = File.expand_path(@options[:derived_data_path])
  test_session_logs = Dir.glob("#{derived_data_path}/Logs/Test/*.xcresult/*_Test/Diagnostics/**/Session-*.log")
  return '' if test_session_logs.empty?

  test_session_logs.sort! { |logfile1, logfile2| File.mtime(logfile1) <=> File.mtime(logfile2) }
  test_session = File.open(test_session_logs.last)
  backwards_seek_offset = -1 * [1000, test_session.stat.size].min
  test_session.seek(backwards_seek_offset, IO::SEEK_END)
  test_session_last_messages = test_session.read
end

#move_simulator_logs_for_next_runObject



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 335

def move_simulator_logs_for_next_run
  return unless @options[:include_simulator_logs]

  glob_pattern = "#{output_directory}/system_logs-*.{log,logarchive}"
  logs = Dir.glob(glob_pattern)
  batch_prefix = ''
  if @options[:batch]
    batch_prefix = "batch-#{@options[:batch]}-"
  end
  logs.each do |log_filepath|
    new_logname = "#{batch_prefix}try-#{testrun_count}-#{File.basename(log_filepath)}"
    new_log_filepath = "#{File.dirname(log_filepath)}/#{new_logname}"
    FastlaneCore::UI.verbose("Moving simulator log '#{log_filepath}' to '#{new_log_filepath}'")
    FileUtils.mv(log_filepath, new_log_filepath, force: true)
  end
end

#move_test_result_bundle_for_next_runObject



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/retrying_scan_helper.rb', line 352

def move_test_result_bundle_for_next_run
  return unless @options[:result_bundle]

  result_extension = FastlaneCore::Helper.xcode_at_least?('11') ? '.xcresult' : '.test_result'
  
  glob_pattern = "#{output_directory}/*#{result_extension}"
  preexisting_test_result_bundles = Dir.glob(glob_pattern)
  unnumbered_test_result_bundles = preexisting_test_result_bundles.reject do |test_result|
    test_result =~ /.*-\d+\#{result_extension}/
  end
  src_test_bundle = unnumbered_test_result_bundles.first
  dst_test_bundle_parent_dir = File.dirname(src_test_bundle)
  dst_test_bundle_basename = File.basename(src_test_bundle, result_extension)
  dst_test_bundle = "#{dst_test_bundle_parent_dir}/#{dst_test_bundle_basename}-#{@testrun_count}#{result_extension}"
  FastlaneCore::UI.verbose("Moving test_result '#{src_test_bundle}' to '#{dst_test_bundle}'")
  File.rename(src_test_bundle, dst_test_bundle)
end

#output_directoryObject



58
59
60
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 58

def output_directory
  @options.fetch(:output_directory, 'test_results')
end


62
63
64
65
66
67
68
69
70
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 62

def print_starting_scan_message
  if @options[:only_testing]
    scan_message = "Starting scan ##{@testrun_count + 1} with #{@options.fetch(:only_testing, []).size} tests"
  else
    scan_message = "Starting scan ##{@testrun_count + 1}"
  end
  scan_message << " for batch ##{@options[:batch]}" unless @options[:batch].nil?
  FastlaneCore::UI.message("#{scan_message}.")
end

#quit_simulatorObject



28
29
30
31
32
33
34
35
36
37
38
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 28

def quit_simulator
  return unless @options[:quit_simulators]

  @options.fetch(:destination).each do |destination|
    if /id=(?<udid>[^,$]+)/ =~ destination
      FastlaneCore::UI.verbose("Restarting Simulator #{udid}")
      `xcrun simctl shutdown #{udid} 2>/dev/null`
      `xcrun simctl boot #{udid} 2>/dev/null`
    end
  end
end

#reset_json_envObject



83
84
85
86
87
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 83

def reset_json_env
  return unless @reportnamer.includes_json?

  ENV['XCPRETTY_JSON_FILE_OUTPUT'] = @xcpretty_json_file_output
end

#retrieve_test_operation_failure(test_session_last_messages) ⇒ Object



294
295
296
297
298
299
300
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 294

def retrieve_test_operation_failure(test_session_last_messages)
  if FastlaneCore::Helper.xcode_at_least?('11')
    retrieve_test_operation_failure_post_xcode11(test_session_last_messages)
  else
    retrieve_test_operation_failure_pre_xcode11(test_session_last_messages)
  end
end

#retrieve_test_operation_failure_post_xcode11(test_session_last_messages) ⇒ Object



302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 302

def retrieve_test_operation_failure_post_xcode11(test_session_last_messages)
  if /Connection peer refused channel request/ =~ test_session_last_messages
    test_operation_failure = 'Lost connection to testmanagerd'
  elsif /Please unlock your device and reattach/ =~ test_session_last_messages
    test_operation_failure = 'Test device locked'
  elsif /Test runner exited before starting test execution/ =~ test_session_last_messages
    test_operation_failure = 'Test runner exited before starting test execution'
  else
    test_operation_failure = 'Unknown test operation failure'
  end
  test_operation_failure
end

#retrieve_test_operation_failure_pre_xcode11(test_session_last_messages) ⇒ Object



315
316
317
318
319
320
321
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 315

def retrieve_test_operation_failure_pre_xcode11(test_session_last_messages)
  test_operation_failure_match = /Test operation failure: (?<test_operation_failure>.*)$/ =~ test_session_last_messages
  if test_operation_failure_match.nil?
    test_operation_failure = 'Unknown test operation failure'
  end
  test_operation_failure
end

#scan_optionsObject



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

def scan_options
  valid_scan_keys = Fastlane::Actions::ScanAction.available_options.map(&:key)
  xcargs = @options[:xcargs] || ''
  if xcargs&.include?('build-for-testing')
    FastlaneCore::UI.important(":xcargs, #{xcargs}, contained 'build-for-testing', removing it")
    xcargs.slice!('build-for-testing')
  end
  if xcargs.include?('-quiet')
    FastlaneCore::UI.important('Disabling -quiet as failing tests cannot be found with it enabled.')
    xcargs.gsub!('-quiet', '')
  end
  xcargs.gsub!(/-parallel-testing-enabled(=|\s+)(YES|NO)/, '')
  retrying_scan_options = @reportnamer.scan_options.merge(
    {
      output_directory: output_directory,
      xcargs: "#{xcargs} -parallel-testing-enabled NO "
    }
  )
  if @reportnamer.includes_xcresult?
    retrying_scan_options[:xcargs] += "-resultBundlePath '#{File.join(output_directory, @reportnamer.xcresult_last_bundlename)}' "
  end

  @options.select { |k,v| valid_scan_keys.include?(k) }
    .merge(retrying_scan_options)
end

#send_callback_testrun_info(additional_info = {}) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 174

def send_callback_testrun_info(additional_info = {})
  return unless @options[:testrun_completed_block]

  report_filepath = nil
  junit_results, report_filepath = failure_details(additional_info)

  info = {
    failed: junit_results.fetch(:failed, []),
    passing: junit_results.fetch(:passing, []),
    batch: @options[:batch] || 1,
    try_count: @testrun_count,
    report_filepath: report_filepath
  }.merge(additional_info)

  update_html_failure_details(info)
  update_json_failure_details(info)
  update_test_result_bundle_details(info)

  @options[:testrun_completed_block].call(info)
end

#set_json_envObject



72
73
74
75
76
77
78
79
80
81
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 72

def set_json_env
  return unless @reportnamer.includes_json?

  xcpretty_json_file_output = File.join(
    output_directory,
    @reportnamer.json_last_reportname
  )
  FastlaneCore::UI.verbose("Setting the XCPRETTY_JSON_FILE_OUTPUT to #{xcpretty_json_file_output}")
  ENV['XCPRETTY_JSON_FILE_OUTPUT'] = xcpretty_json_file_output
end

#turn_off_code_coverageObject



238
239
240
241
242
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 238

def turn_off_code_coverage
  # Turn off code coverage as code coverage reports are not merged and
  # the first, more valuable, report will be overwritten
  @options.delete(:code_coverage)
end

#update_html_failure_details(info) ⇒ Object



210
211
212
213
214
215
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 210

def update_html_failure_details(info)
  return unless @reportnamer.includes_html?

  html_report_filepath = File.join(output_directory, @reportnamer.html_last_reportname)
  info[:html_report_filepath] = html_report_filepath
end

#update_json_failure_details(info) ⇒ Object



217
218
219
220
221
222
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 217

def update_json_failure_details(info)
  return unless @reportnamer.includes_json?

  json_report_filepath = File.join(output_directory, @reportnamer.json_last_reportname)
  info[:json_report_filepath] = json_report_filepath
end

#update_only_testingObject



244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 244

def update_only_testing
  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] = (@options[:only_testing] || []) - Fastlane::Actions::TestsFromJunitAction.run(config).fetch(:passing, Hash.new).map(&:shellsafe_testidentifier)
  if @options[:invocation_based_tests]
    @options[:only_testing] = @options[:only_testing].map(&:strip_testcase).uniq
  end
end

#update_scan_optionsObject



233
234
235
236
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 233

def update_scan_options
  update_only_testing
  turn_off_code_coverage
end

#update_test_result_bundle_details(info) ⇒ Object



224
225
226
227
228
229
230
231
# File 'lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb', line 224

def update_test_result_bundle_details(info)
  return unless @options[:result_bundle]

  test_result_suffix = '.test_result'
  test_result_suffix.prepend("-#{@reportnamer.report_count}") unless @reportnamer.report_count.zero?
  test_result_bundlepath = File.join(output_directory, @options[:scheme]) + test_result_suffix
  info[:test_result_bundlepath] = test_result_bundlepath
end