fastlane-plugin-test_center 3.7.0 → 3.8.0.parallelizing.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +71 -6
  3. data/lib/fastlane/plugin/test_center/actions/collate_html_reports.rb +2 -0
  4. data/lib/fastlane/plugin/test_center/actions/collate_json_reports.rb +2 -0
  5. data/lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb +2 -1
  6. data/lib/fastlane/plugin/test_center/actions/collate_test_result_bundles.rb +2 -0
  7. data/lib/fastlane/plugin/test_center/actions/multi_scan.rb +127 -32
  8. data/lib/fastlane/plugin/test_center/actions/quit_core_simulator_service.rb +40 -0
  9. data/lib/fastlane/plugin/test_center/actions/suppress_tests.rb +2 -0
  10. data/lib/fastlane/plugin/test_center/actions/suppress_tests_from_junit.rb +3 -1
  11. data/lib/fastlane/plugin/test_center/actions/suppressed_tests.rb +2 -0
  12. data/lib/fastlane/plugin/test_center/actions/tests_from_junit.rb +2 -0
  13. data/lib/fastlane/plugin/test_center/actions/tests_from_xctestrun.rb +2 -0
  14. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/device_manager.rb +30 -0
  15. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/parallel_test_batch_worker.rb +73 -0
  16. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/report_collator.rb +129 -0
  17. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan.rb +77 -0
  18. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb +272 -0
  19. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb +109 -0
  20. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/simulator_helper.rb +49 -0
  21. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/test_batch_worker.rb +24 -0
  22. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/test_batch_worker_pool.rb +133 -0
  23. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager.rb +3 -0
  24. data/lib/fastlane/plugin/test_center/helper/reportname_helper.rb +15 -6
  25. data/lib/fastlane/plugin/test_center/helper/test_collector.rb +55 -14
  26. data/lib/fastlane/plugin/test_center/helper/xctestrun_info.rb +42 -0
  27. data/lib/fastlane/plugin/test_center/version.rb +1 -1
  28. data/lib/fastlane/plugin/test_center.rb +1 -1
  29. metadata +22 -11
  30. data/lib/fastlane/plugin/test_center/helper/correcting_scan_helper.rb +0 -315
@@ -0,0 +1,129 @@
1
+ module TestCenter
2
+ module Helper
3
+ module MultiScanManager
4
+ class ReportCollator
5
+ CollateJunitReportsAction = Fastlane::Actions::CollateJunitReportsAction
6
+ CollateHtmlReportsAction = Fastlane::Actions::CollateHtmlReportsAction
7
+ CollateJsonReportsAction = Fastlane::Actions::CollateJsonReportsAction
8
+ CollateTestResultBundlesAction = Fastlane::Actions::CollateTestResultBundlesAction
9
+
10
+ def initialize(params)
11
+ @source_reports_directory_glob = params[:source_reports_directory_glob]
12
+ @output_directory = params[:output_directory]
13
+ @reportnamer = params[:reportnamer]
14
+ @scheme = params[:scheme]
15
+ @result_bundle = params[:result_bundle]
16
+ @suffix = params[:suffix] || ''
17
+ end
18
+
19
+ def collate
20
+ collate_junit_reports
21
+ collate_html_reports
22
+ collate_json_reports
23
+ collate_test_result_bundles
24
+ end
25
+
26
+ def sort_globbed_files(glob)
27
+ files = Dir.glob(glob).map do |relative_filepath|
28
+ File.absolute_path(relative_filepath)
29
+ end
30
+ files.sort! { |f1, f2| File.mtime(f1) <=> File.mtime(f2) }
31
+ end
32
+
33
+ def delete_globbed_intermediatefiles(glob)
34
+ retried_reportfiles = Dir.glob(glob)
35
+ FileUtils.rm_f(retried_reportfiles)
36
+ end
37
+
38
+ # :nocov:
39
+ def create_config(klass, options)
40
+ FastlaneCore::Configuration.create(klass.available_options, options)
41
+ end
42
+ # :nocov:
43
+
44
+ def collate_junit_reports
45
+ glob = "#{@source_reports_directory_glob}/#{@reportnamer.junit_fileglob}"
46
+ report_files = sort_globbed_files(glob)
47
+ collated_file = File.absolute_path(File.join(@output_directory, @reportnamer.junit_reportname(@suffix)))
48
+ if report_files.size > 1
49
+ config = create_config(
50
+ CollateJunitReportsAction,
51
+ {
52
+ reports: report_files,
53
+ collated_report: collated_file
54
+ }
55
+ )
56
+ CollateJunitReportsAction.run(config)
57
+ FileUtils.rm_rf(report_files - [collated_file])
58
+ elsif report_files.size == 1 && report_files.first != collated_file
59
+ FileUtils.mkdir_p(File.dirname(collated_file))
60
+ FileUtils.mv(report_files.first, collated_file)
61
+ end
62
+ end
63
+
64
+ def collate_html_reports
65
+ return unless @reportnamer.includes_html?
66
+
67
+ report_files = sort_globbed_files("#{@source_reports_directory_glob}/#{@reportnamer.html_fileglob}")
68
+ collated_file = File.absolute_path(File.join(@output_directory, @reportnamer.html_reportname(@suffix)))
69
+ if report_files.size > 1
70
+ config = create_config(
71
+ CollateJunitReportsAction,
72
+ {
73
+ reports: report_files,
74
+ collated_report: collated_file
75
+ }
76
+ )
77
+ CollateHtmlReportsAction.run(config)
78
+ FileUtils.rm_rf(report_files - [collated_file])
79
+ elsif report_files.size == 1 && report_files.first != collated_file
80
+ FileUtils.mkdir_p(File.dirname(collated_file))
81
+ FileUtils.mv(report_files.first, collated_file)
82
+ end
83
+ end
84
+
85
+ def collate_json_reports
86
+ return unless @reportnamer.includes_json?
87
+
88
+ report_files = sort_globbed_files("#{@source_reports_directory_glob}/#{@reportnamer.json_fileglob}")
89
+ collated_file = File.absolute_path(File.join(@output_directory, @reportnamer.json_reportname(@suffix)))
90
+ if report_files.size > 1
91
+ config = create_config(
92
+ CollateJsonReportsAction,
93
+ {
94
+ reports: report_files,
95
+ collated_report: collated_file
96
+ }
97
+ )
98
+ CollateJsonReportsAction.run(config)
99
+ FileUtils.rm_rf(report_files - [collated_file])
100
+ elsif report_files.size == 1 && report_files.first != collated_file
101
+ FileUtils.mkdir_p(File.dirname(collated_file))
102
+ FileUtils.mv(report_files.first, collated_file)
103
+ end
104
+ end
105
+
106
+ def collate_test_result_bundles
107
+ return unless @result_bundle
108
+
109
+ test_result_bundlepaths = sort_globbed_files("#{@source_reports_directory_glob}/#{@scheme}*.test_result")
110
+ collated_test_result_bundlepath = File.absolute_path("#{File.join(@output_directory, @scheme)}.test_result")
111
+ if test_result_bundlepaths.size > 1
112
+ config = create_config(
113
+ CollateTestResultBundlesAction,
114
+ {
115
+ bundles: test_result_bundlepaths,
116
+ collated_bundle: collated_test_result_bundlepath
117
+ }
118
+ )
119
+ CollateTestResultBundlesAction.run(config)
120
+ FileUtils.rm_rf(test_result_bundlepaths - [collated_test_result_bundlepath])
121
+ elsif test_result_bundlepaths.size == 1 && test_result_bundlepaths.first != collated_test_result_bundlepath
122
+ FileUtils.mkdir_p(File.dirname(collated_test_result_bundlepath))
123
+ FileUtils.mv(test_result_bundlepaths.first, collated_test_result_bundlepath)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,77 @@
1
+ module TestCenter
2
+ module Helper
3
+ module MultiScanManager
4
+ class RetryingScan
5
+ def initialize(options = {})
6
+ @options = options
7
+ @retrying_scan_helper = RetryingScanHelper.new(@options)
8
+ end
9
+
10
+ # :nocov:
11
+ def scan_config
12
+ Scan.config
13
+ end
14
+
15
+ def scan_cache
16
+ Scan.cache
17
+ end
18
+ # :nocov:
19
+
20
+ def prepare_scan_config_for_destination
21
+ # this allows multi_scan's `destination` option to be picked up by `scan`
22
+ scan_config._values.delete(:device)
23
+ scan_config._values.delete(:devices)
24
+ scan_cache.clear
25
+ end
26
+
27
+ def update_scan_options
28
+ valid_scan_keys = Fastlane::Actions::ScanAction.available_options.map(&:key)
29
+ scan_options = @options.select { |k,v| valid_scan_keys.include?(k) }
30
+ .merge(@retrying_scan_helper.scan_options)
31
+
32
+ prepare_scan_config_for_destination
33
+ scan_options.each do |k,v|
34
+ scan_config.set(k,v) unless v.nil?
35
+ end
36
+ end
37
+
38
+ # :nocov:
39
+ def self.run(options)
40
+ RetryingScan.new(options).run
41
+ end
42
+ # :nocov:
43
+
44
+ def run
45
+ try_count = @options[:try_count] || 1
46
+ begin
47
+ # TODO move delete_xcresults to `before_testrun`
48
+ @retrying_scan_helper.before_testrun
49
+ update_scan_options
50
+
51
+ values = scan_config.values(ask: false)
52
+ values[:xcode_path] = File.expand_path("../..", FastlaneCore::Helper.xcode_path)
53
+ FastlaneCore::PrintTable.print_values(
54
+ config: values,
55
+ hide_keys: [:destination, :slack_url],
56
+ title: "Summary for scan #{Fastlane::VERSION}"
57
+ ) unless FastlaneCore::Helper.test?
58
+
59
+ Scan::Runner.new.run
60
+ @retrying_scan_helper.after_testrun
61
+ true
62
+ rescue FastlaneCore::Interface::FastlaneTestFailure => e
63
+ FastlaneCore::UI.message("retrying_scan after test failure")
64
+ @retrying_scan_helper.after_testrun(e)
65
+ retry if @retrying_scan_helper.testrun_count < try_count
66
+ false
67
+ rescue FastlaneCore::Interface::FastlaneBuildFailure => e
68
+ FastlaneCore::UI.message("retrying_scan after build failure")
69
+ @retrying_scan_helper.after_testrun(e)
70
+ retry if @retrying_scan_helper.testrun_count < try_count
71
+ false
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,272 @@
1
+ module TestCenter
2
+ module Helper
3
+ module MultiScanManager
4
+ require_relative 'device_manager'
5
+
6
+ class RetryingScanHelper
7
+
8
+ attr_reader :testrun_count
9
+
10
+ def initialize(options)
11
+ raise ArgumentError, 'Do not use the :device or :devices option. Instead use the :destination option.' if (options.key?(:device) or options.key?(:devices))
12
+
13
+ @options = options
14
+ @testrun_count = 0
15
+ @xcpretty_json_file_output = ENV['XCPRETTY_JSON_FILE_OUTPUT']
16
+ @reportnamer = ReportNameHelper.new(
17
+ @options[:output_types],
18
+ @options[:output_files],
19
+ @options[:custom_report_file_name]
20
+ )
21
+ end
22
+
23
+ def before_testrun
24
+ remove_preexisting_test_result_bundles
25
+ delete_xcresults # has to be performed _after_ moving a *.test_result
26
+ quit_simulator
27
+ set_json_env
28
+ print_starting_scan_message
29
+ end
30
+
31
+ def quit_simulator
32
+ return unless @options[:quit_simulators]
33
+
34
+ @options.fetch(:destination).each do |destination|
35
+ if /id=(?<udid>\w+),?/ =~ destination
36
+ `xcrun simctl shutdown #{udid} 2>/dev/null`
37
+ end
38
+ end
39
+ end
40
+
41
+ def delete_xcresults
42
+ derived_data_path = File.expand_path(@options[:derived_data_path] || Scan.config[:derived_data_path])
43
+ xcresults = Dir.glob("#{derived_data_path}/Logs/Test/*.xcresult")
44
+ FastlaneCore::UI.message("Deleting xcresults: #{xcresults}")
45
+ FileUtils.rm_rf(xcresults)
46
+ end
47
+
48
+ def output_directory
49
+ absolute_output_directory = File.absolute_path(@options[:output_directory])
50
+ if @options[:batch]
51
+ testable = @options.fetch(:only_testing, ['']).first.split('/').first || ''
52
+ absolute_output_directory = File.join(absolute_output_directory, "#{testable}-batch-#{@options[:batch]}")
53
+ end
54
+ absolute_output_directory
55
+ end
56
+
57
+ def print_starting_scan_message
58
+ scan_message = "Starting scan ##{@testrun_count + 1} with #{@options.fetch(:only_testing, []).size} tests"
59
+ scan_message << " for batch ##{@options[:batch]}" unless @options[:batch].nil?
60
+ FastlaneCore::UI.message("#{scan_message}.")
61
+ end
62
+
63
+ def set_json_env
64
+ return unless @reportnamer.includes_json?
65
+
66
+ ENV['XCPRETTY_JSON_FILE_OUTPUT'] = File.join(
67
+ output_directory,
68
+ @reportnamer.json_last_reportname
69
+ )
70
+ end
71
+
72
+ def reset_json_env
73
+ return unless @reportnamer.includes_json?
74
+
75
+ ENV['XCPRETTY_JSON_FILE_OUTPUT'] = @xcpretty_json_file_output
76
+ end
77
+
78
+ def remove_preexisting_test_result_bundles
79
+ return unless @options[:result_bundle]
80
+
81
+ glob_pattern = "#{output_directory}/*.test_result"
82
+ preexisting_test_result_bundles = Dir.glob(glob_pattern)
83
+ FileUtils.rm_rf(preexisting_test_result_bundles)
84
+ end
85
+
86
+ def scan_options
87
+ valid_scan_keys = Fastlane::Actions::ScanAction.available_options.map(&:key)
88
+ xcargs = @options[:xcargs]
89
+ retrying_scan_options = @reportnamer.scan_options.merge(
90
+ {
91
+ output_directory: output_directory,
92
+ xcargs: "#{xcargs} -parallel-testing-enabled NO"
93
+ }
94
+ )
95
+ @options.select { |k,v| valid_scan_keys.include?(k) }
96
+ .merge(retrying_scan_options)
97
+ end
98
+
99
+ # after_testrun methods
100
+
101
+ def after_testrun(exception = nil)
102
+ @testrun_count = @testrun_count + 1
103
+ if exception.kind_of?(FastlaneCore::Interface::FastlaneTestFailure)
104
+ handle_test_failure
105
+ elsif exception.kind_of?(FastlaneCore::Interface::FastlaneBuildFailure)
106
+ handle_build_failure(exception)
107
+ else
108
+ handle_success
109
+ end
110
+ collate_reports
111
+ end
112
+
113
+ def handle_success
114
+ send_callback_testrun_info
115
+ move_test_result_bundle_for_next_run
116
+ reset_json_env
117
+ end
118
+
119
+ def collate_reports
120
+ report_collator_options = {
121
+ source_reports_directory_glob: output_directory,
122
+ output_directory: output_directory,
123
+ reportnamer: @reportnamer,
124
+ scheme: @options[:scheme],
125
+ result_bundle: @options[:result_bundle]
126
+ }
127
+ TestCenter::Helper::MultiScanManager::ReportCollator.new(report_collator_options).collate
128
+ end
129
+
130
+ def handle_test_failure
131
+ send_callback_testrun_info
132
+ reset_simulators
133
+ move_test_result_bundle_for_next_run
134
+ update_scan_options
135
+ @reportnamer.increment
136
+ end
137
+
138
+ def send_callback_testrun_info(additional_info = {})
139
+ return unless @options[:testrun_completed_block]
140
+
141
+ report_filepath = nil
142
+ junit_results = {}
143
+ unless additional_info.key?(:test_operation_failure)
144
+ report_filepath = File.join(output_directory, @reportnamer.junit_last_reportname)
145
+
146
+ config = FastlaneCore::Configuration.create(
147
+ Fastlane::Actions::TestsFromJunitAction.available_options,
148
+ {
149
+ junit: File.absolute_path(report_filepath)
150
+ }
151
+ )
152
+ junit_results = Fastlane::Actions::TestsFromJunitAction.run(config)
153
+ end
154
+
155
+ info = {
156
+ failed: junit_results[:failed],
157
+ passing: junit_results[:passing],
158
+ batch: @options[:batch] || 1,
159
+ try_count: @testrun_count,
160
+ report_filepath: report_filepath
161
+ }.merge(additional_info)
162
+
163
+ if @reportnamer.includes_html?
164
+ html_report_filepath = File.join(output_directory, @reportnamer.html_last_reportname)
165
+ info[:html_report_filepath] = html_report_filepath
166
+ end
167
+ if @reportnamer.includes_json?
168
+ json_report_filepath = File.join(output_directory, @reportnamer.json_last_reportname)
169
+ info[:json_report_filepath] = json_report_filepath
170
+ end
171
+ if @options[:result_bundle]
172
+ test_result_suffix = '.test_result'
173
+ test_result_suffix.prepend("-#{@reportnamer.report_count}") unless @reportnamer.report_count.zero?
174
+ test_result_bundlepath = File.join(output_directory, @options[:scheme]) + test_result_suffix
175
+ info[:test_result_bundlepath] = test_result_bundlepath
176
+ end
177
+ @options[:testrun_completed_block].call(info)
178
+ end
179
+
180
+ def update_scan_options
181
+ update_only_testing
182
+ turn_off_code_coverage
183
+ end
184
+
185
+ def turn_off_code_coverage
186
+ # Turn off code coverage as code coverage reports are not merged and
187
+ # the first, more valuable, report will be overwritten
188
+ @options.delete(:code_coverage)
189
+ end
190
+
191
+ def update_only_testing
192
+ report_filepath = File.join(output_directory, @reportnamer.junit_last_reportname)
193
+ config = FastlaneCore::Configuration.create(
194
+ Fastlane::Actions::TestsFromJunitAction.available_options,
195
+ {
196
+ junit: File.absolute_path(report_filepath)
197
+ }
198
+ )
199
+ @options[:only_testing] = Fastlane::Actions::TestsFromJunitAction.run(config)[:failed]
200
+ if @options[:invocation_based_tests]
201
+ @options[:only_testing] = @options[:only_testing].map(&:strip_testcase).uniq
202
+ end
203
+ end
204
+
205
+ def reset_simulators
206
+ return unless @options[:reset_simulators]
207
+
208
+ @options[:simulators].each(&:reset)
209
+ end
210
+
211
+ def handle_build_failure(exception)
212
+ test_operation_failure = ''
213
+
214
+ test_session_last_messages = last_lines_of_test_session_log
215
+ test_operation_failure_match = /Test operation failure: (?<test_operation_failure>.*)$/ =~ test_session_last_messages
216
+ if test_operation_failure_match.nil?
217
+ test_operation_failure = 'Unknown test operation failure'
218
+ end
219
+
220
+ case test_operation_failure
221
+ when /Test runner exited before starting test execution/
222
+ FastlaneCore::UI.error(test_operation_failure)
223
+ when /Lost connection to testmanagerd/
224
+ FastlaneCore::UI.error(test_operation_failure)
225
+ FastlaneCore::UI.important("com.apple.CoreSimulator.CoreSimulatorService may have become corrupt, consider quitting it")
226
+ if @options[:quit_core_simulator_service]
227
+ Fastlane::Actions::RestartCoreSimulatorServiceAction.run
228
+ end
229
+ else
230
+ FastlaneCore::UI.error(test_operation_failure)
231
+ send_callback_testrun_info(test_operation_failure: test_operation_failure)
232
+ raise exception
233
+ end
234
+ if @options[:reset_simulators]
235
+ @options[:simulators].each do |simulator|
236
+ simulator.reset
237
+ end
238
+ end
239
+ send_callback_testrun_info(test_operation_failure: test_operation_failure)
240
+ end
241
+
242
+ def last_lines_of_test_session_log
243
+ derived_data_path = File.expand_path(@options[:derived_data_path])
244
+ test_session_logs = Dir.glob("#{derived_data_path}/Logs/Test/*.xcresult/*_Test/Diagnostics/**/Session-*.log")
245
+ return '' if test_session_logs.empty?
246
+
247
+ test_session_logs.sort! { |logfile1, logfile2| File.mtime(logfile1) <=> File.mtime(logfile2) }
248
+ test_session = File.open(test_session_logs.last)
249
+ backwards_seek_offset = -1 * [1000, test_session.stat.size].min
250
+ test_session.seek(backwards_seek_offset, IO::SEEK_END)
251
+ test_session_last_messages = test_session.read
252
+ end
253
+
254
+ def move_test_result_bundle_for_next_run
255
+ return unless @options[:result_bundle]
256
+
257
+ glob_pattern = "#{output_directory}/*.test_result"
258
+ preexisting_test_result_bundles = Dir.glob(glob_pattern)
259
+ unnumbered_test_result_bundles = preexisting_test_result_bundles.reject do |test_result|
260
+ test_result =~ /.*-\d+\.test_result/
261
+ end
262
+ src_test_bundle = unnumbered_test_result_bundles.first
263
+ dst_test_bundle_parent_dir = File.dirname(src_test_bundle)
264
+ dst_test_bundle_basename = File.basename(src_test_bundle, '.test_result')
265
+ dst_test_bundle = "#{dst_test_bundle_parent_dir}/#{dst_test_bundle_basename}-#{@testrun_count}.test_result"
266
+ FileUtils.mkdir_p(dst_test_bundle)
267
+ FileUtils.mv(src_test_bundle, dst_test_bundle)
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,109 @@
1
+
2
+ module TestCenter
3
+ module Helper
4
+ module MultiScanManager
5
+ require 'fastlane_core/ui/ui.rb'
6
+ require 'plist'
7
+ require 'json'
8
+ require 'shellwords'
9
+ require 'snapshot/reset_simulators'
10
+
11
+ class Runner
12
+ attr_reader :retry_total_count
13
+
14
+ def initialize(multi_scan_options)
15
+ @test_collector = TestCenter::Helper::TestCollector.new(multi_scan_options)
16
+ @options = multi_scan_options.merge(
17
+ clean: false,
18
+ disable_concurrent_testing: true
19
+ )
20
+ @batch_count = @test_collector.test_batches.size
21
+ end
22
+
23
+ def output_directory
24
+ @options.fetch(:output_directory, 'test_results')
25
+ end
26
+
27
+ def run
28
+ if @options[:invocation_based_tests]
29
+ run_invocation_based_tests
30
+ else
31
+ run_test_batches
32
+ end
33
+ end
34
+
35
+ def run_invocation_based_tests
36
+ @options[:only_testing] = @options[:only_testing]&.map(&:strip_testcase)&.uniq
37
+ @options[:skip_testing] = @options[:skip_testing]&.map(&:strip_testcase)&.uniq
38
+
39
+ RetryingScan.run(@options.reject { |key| %i[device devices force_quit_simulator].include?(key) } )
40
+ end
41
+
42
+ def run_test_batches
43
+ test_batch_results = []
44
+ pool_options = @options.reject { |key| %i[device devices force_quit_simulator].include?(key) }
45
+ pool_options[:test_batch_results] = test_batch_results
46
+
47
+ pool = TestBatchWorkerPool.new(pool_options)
48
+ pool.setup_workers
49
+
50
+ remaining_test_batches = @test_collector.test_batches.clone
51
+ remaining_test_batches.each_with_index do |test_batch, current_batch_index|
52
+ worker = pool.wait_for_worker
53
+ FastlaneCore::UI.message("Starting test run #{current_batch_index}")
54
+ worker.run(scan_options_for_worker(test_batch, current_batch_index))
55
+ end
56
+ pool.wait_for_all_workers
57
+ collate_batched_reports
58
+ test_batch_results.reduce(true) { |a, t| a && t }
59
+ end
60
+
61
+ def scan_options_for_worker(test_batch, batch_index)
62
+ {
63
+ only_testing: test_batch.map(&:shellsafe_testidentifier),
64
+ output_directory: output_directory,
65
+ try_count: @options[:try_count],
66
+ batch: batch_index + 1,
67
+ xctestrun: @test_collector.xctestrun_path
68
+ }
69
+ end
70
+
71
+ def collate_batched_reports
72
+ return false unless @batch_count > 1
73
+
74
+ @test_collector.testables.each do |testable|
75
+ collate_batched_reports_for_testable(testable)
76
+ end
77
+ true
78
+ end
79
+
80
+ def collate_batched_reports_for_testable(testable)
81
+ absolute_output_directory = File.join(
82
+ File.absolute_path(output_directory),
83
+ testable
84
+ )
85
+ source_reports_directory_glob = "#{absolute_output_directory}-batch-*"
86
+
87
+ given_custom_report_file_name = @options[:custom_report_file_name]
88
+ given_output_types = @options[:output_types]
89
+ given_output_files = @options[:output_files]
90
+
91
+ TestCenter::Helper::MultiScanManager::ReportCollator.new(
92
+ source_reports_directory_glob: source_reports_directory_glob,
93
+ output_directory: absolute_output_directory,
94
+ reportnamer: ReportNameHelper.new(
95
+ given_output_types,
96
+ given_output_files,
97
+ given_custom_report_file_name
98
+ ),
99
+ scheme: @options[:scheme],
100
+ result_bundle: @options[:result_bundle]
101
+ ).collate
102
+ FileUtils.rm_rf(Dir.glob(source_reports_directory_glob))
103
+ true
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+
@@ -0,0 +1,49 @@
1
+ module TestCenter
2
+ module Helper
3
+ module MultiScanManager
4
+ class SimulatorHelper
5
+ def initialize(options)
6
+ @options = options
7
+ end
8
+
9
+ def setup
10
+ if @options[:parallel_testrun_count] > 1
11
+ delete_multi_scan_cloned_simulators
12
+ end
13
+ end
14
+
15
+ def clone_destination_simulators
16
+ cloned_simulators = []
17
+
18
+ run_count = @options[:parallel_testrun_count] || 0
19
+ destination_simulator_ids = Scan.config[:destination].map do |destination|
20
+ destination.split(',id=').last
21
+ end
22
+ original_simulators = FastlaneCore::DeviceManager.simulators('iOS').find_all do |simulator|
23
+ destination_simulator_ids.include?(simulator.udid)
24
+ end
25
+ original_simulators.each(&:shutdown)
26
+ (0...run_count).each do |batch_index|
27
+ cloned_simulators << []
28
+ original_simulators.each do |simulator|
29
+ FastlaneCore::UI.verbose("Cloning simulator")
30
+ cloned_simulator = simulator.clone
31
+ new_first_name = simulator.name.sub(/( ?\(.*| ?$)/, " Clone #{batch_index}")
32
+ new_last_name = "#{self.class.name}<#{self.object_id}>"
33
+ cloned_simulator.rename("#{new_first_name} #{new_last_name}")
34
+
35
+ cloned_simulators.last << cloned_simulator
36
+ end
37
+ end
38
+ cloned_simulators
39
+ end
40
+
41
+ def delete_multi_scan_cloned_simulators
42
+ FastlaneCore::DeviceManager.simulators('iOS').each do |simulator|
43
+ simulator.delete if /#{self.class.name}<\d+>/ =~ simulator.name
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,24 @@
1
+ module TestCenter
2
+ module Helper
3
+ module MultiScanManager
4
+ class TestBatchWorker
5
+ attr_accessor :state
6
+
7
+ def initialize(options)
8
+ @options = options
9
+ @state = :ready_to_work
10
+ end
11
+
12
+ def process_results
13
+ @state = :ready_to_work
14
+ end
15
+
16
+ def run(run_options)
17
+ self.state = :working
18
+ @options[:test_batch_results] << RetryingScan.run(@options.merge(run_options))
19
+ self.state = :ready_to_work
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end