fastlane-plugin-test_center 3.6.2 → 3.6.3.parallelizing

Sign up to get free protection for your applications and to get access to all the features.
Files changed (22) hide show
  1. checksums.yaml +5 -5
  2. data/lib/fastlane/plugin/test_center.rb +1 -1
  3. data/lib/fastlane/plugin/test_center/actions/collate_test_result_bundles.rb +1 -1
  4. data/lib/fastlane/plugin/test_center/actions/multi_scan.rb +28 -4
  5. data/lib/fastlane/plugin/test_center/actions/restart_core_simulator_service.rb +37 -0
  6. data/lib/fastlane/plugin/test_center/actions/tests_from_xctestrun.rb +1 -1
  7. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager.rb +5 -0
  8. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/device_manager.rb +26 -0
  9. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/interstitial.rb +143 -0
  10. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/report_collator.rb +113 -0
  11. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan.rb +72 -0
  12. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb +236 -0
  13. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb +272 -0
  14. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/simulator_helper.rb +59 -0
  15. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/simulator_manager.rb +317 -0
  16. data/lib/fastlane/plugin/test_center/helper/reportname_helper.rb +15 -6
  17. data/lib/fastlane/plugin/test_center/helper/test_collector.rb +47 -3
  18. data/lib/fastlane/plugin/test_center/helper/xcodebuild_string.rb +9 -0
  19. data/lib/fastlane/plugin/test_center/helper/xctestrun_info.rb +42 -0
  20. data/lib/fastlane/plugin/test_center/version.rb +1 -1
  21. metadata +21 -12
  22. data/lib/fastlane/plugin/test_center/helper/correcting_scan_helper.rb +0 -293
@@ -0,0 +1,72 @@
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
+ def delete_xcresults
11
+ derived_data_path = File.expand_path(@options.fetch(:derived_data_path, ''))
12
+ xcresults = Dir.glob("#{derived_data_path}/Logs/Test/*.xcresult")
13
+ FileUtils.rm_rf(xcresults)
14
+ end
15
+
16
+ def run
17
+ delete_xcresults
18
+
19
+ try_count = @options[:try_count] || 1
20
+ begin
21
+ @retrying_scan_helper.before_testrun
22
+
23
+ valid_scan_keys = Fastlane::Actions::ScanAction.available_options.map(&:key)
24
+ scan_options = @options.select { |k,v| valid_scan_keys.include?(k) }
25
+ scan_options.each do |k,v|
26
+ Scan.config.set(k,v)
27
+ end
28
+ # TODO: Investigate the following error:
29
+ """
30
+ 2019-05-09 13:32:40.707 xcodebuild[78535:1944070] [MT] DVTAssertions: Warning in /Library/Caches/com.apple.xbs/Sources/IDEFrameworks_Fall2018/IDEFrameworks-14460.46/IDEFoundation/ProjectModel/ActionRecords/IDESchemeActionTestAttachment.m:186
31
+ Details: Error writing attachment data to file /Users/lyndsey.ferguson/Library/Developer/Xcode/DerivedData/AtomicBoy-flqqvvvzbouqymbyffgdbtjoiufr/Logs/Test/Test-Transient Testing-2019.05.09_13-32-08--0400.xcresult/1_Test/Attachments/Screenshot_E0AE7940-E7F4-4CA8-BB2B-8822D730D10F.jpg: Error Domain=NSCocoaErrorDomain Code=4 \"The folder “Screenshot_E0AE7940-E7F4-4CA8-BB2B-8822D730D10F.jpg” doesn’t exist.\" UserInfo={NSFilePath=/Users/lyndsey.ferguson/Library/Developer/Xcode/DerivedData/AtomicBoy-flqqvvvzbouqymbyffgdbtjoiufr/Logs/Test/Test-Transient Testing-2019.05.09_13-32-08--0400.xcresult/1_Test/Attachments/Screenshot_E0AE7940-E7F4-4CA8-BB2B-8822D730D10F.jpg, NSUserStringVariant=Folder, NSUnderlyingError=0x7fa6ef34ef90 {Error Domain=NSPOSIXErrorDomain Code=2 \"No such file or directory\"}}
32
+ Object: <IDESchemeActionTestAttachment: 0x7fa6ef22d270>
33
+ Method: -_savePayload:
34
+ Thread: <NSThread: 0x7fa6ea516110>{number = 1, name = main}
35
+ Please file a bug at https://bugreport.apple.com with this warning message and any useful information you can provide.
36
+
37
+ It may be due to:
38
+ 2019-05-09 14:17:30.933 xcodebuild[86893:2045058] IDETestOperationsObserverDebug: Failed to move logarchive from /Users/lyndsey.ferguson/Library/Developer/CoreSimulator/Devices/0D312041-2D60-4221-94CC-3B0040154D74/data/tmp/test-session-systemlogs-2019.05.09_14-17-04--0400.logarchive to diagnostics location /Users/lyndsey.ferguson/Library/Developer/Xcode/DerivedData/AtomicBoy-flqqvvvzbouqymbyffgdbtjoiufr/Logs/Test/Test-Transient Testing-2019.05.09_14-17-04--0400.xcresult/1_Test/Diagnostics/iPhone 5s_0D312041-2D60-4221-94CC-3B0040154D74/test-session-systemlogs-2019.05.09_14-17-04--0400.logarchive: Error Domain=NSCocoaErrorDomain Code=4 \"“test-session-systemlogs-2019.05.09_14-17-04--0400.logarchive” couldn’t be moved to “iPhone 5s_0D312041-2D60-4221-94CC-3B0040154D74” because either the former doesn’t exist, or the folder containing the latter doesn’t exist.\" UserInfo={NSSourceFilePathErrorKey=/Users/lyndsey.ferguson/Library/Developer/CoreSimulator/Devices/0D312041-2D60-4221-94CC-3B0040154D74/data/tmp/test-session-systemlogs-2019.05.09_14-17-04--0400.logarchive, NSUserStringVariant=(
39
+ Move
40
+ ), NSDestinationFilePath=/Users/lyndsey.ferguson/Library/Developer/Xcode/DerivedData/AtomicBoy-flqqvvvzbouqymbyffgdbtjoiufr/Logs/Test/Test-Transient Testing-2019.05.09_14-17-04--0400.xcresult/1_Test/Diagnostics/iPhone 5s_0D312041-2D60-4221-94CC-3B0040154D74/test-session-systemlogs-2019.05.09_14-17-04--0400.logarchive, NSFilePath=/Users/lyndsey.ferguson/Library/Developer/CoreSimulator/Devices/0D312041-2D60-4221-94CC-3B0040154D74/data/tmp/test-session-systemlogs-2019.05.09_14-17-04--0400.logarchive, NSUnderlyingError=0x7fdf96c6de90 {Error Domain=NSPOSIXErrorDomain Code=2 \"No such file or directory\"}}
41
+
42
+ """
43
+ Scan::Runner.new.run
44
+ @retrying_scan_helper.after_testrun
45
+ true
46
+ rescue FastlaneCore::Interface::FastlaneTestFailure => e
47
+ @retrying_scan_helper.after_testrun(e)
48
+ retry if @retrying_scan_helper.testrun_count < try_count
49
+ false
50
+ rescue FastlaneCore::Interface::FastlaneBuildFailure => e
51
+ @retrying_scan_helper.after_testrun(e)
52
+ retry if @retrying_scan_helper.testrun_count < try_count
53
+ false
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ # Ug. How do I name this class?
61
+ # I want a class that retries a scan
62
+ # I want a class that controls or manages the class that retries the scan and the set up for that
63
+ # etc.
64
+ # So, the class or the realm could be:
65
+ # MultiScanManager
66
+ # MultiScanController
67
+ # ScanManager
68
+ # ScanMaster
69
+ # MultiScanMaster
70
+ # MasterScanManser
71
+ # MasterMultiScan
72
+ # I like MultiScanManager
@@ -0,0 +1,236 @@
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
+
17
+ @reportnamer = ReportNameHelper.new(
18
+ options[:output_types],
19
+ options[:output_files],
20
+ options[:custom_report_file_name]
21
+ )
22
+ end
23
+
24
+ def before_testrun
25
+ remove_preexisting_test_result_bundles
26
+ set_json_env
27
+ print_starting_scan_message
28
+ end
29
+
30
+ def print_starting_scan_message
31
+ scan_message = "Starting scan ##{@testrun_count + 1} with #{@options.fetch(:only_testing, []).size} tests"
32
+ scan_message << " for batch ##{@options[:batch]}" unless @options[:batch].nil?
33
+ FastlaneCore::UI.message("#{scan_message}.")
34
+ end
35
+
36
+ def set_json_env
37
+ return unless @reportnamer.includes_json?
38
+
39
+ ENV['XCPRETTY_JSON_FILE_OUTPUT'] = File.join(
40
+ @options[:output_directory],
41
+ @reportnamer.json_last_reportname
42
+ )
43
+ end
44
+
45
+ def reset_json_env
46
+ return unless @reportnamer.includes_json?
47
+
48
+ ENV['XCPRETTY_JSON_FILE_OUTPUT'] = @xcpretty_json_file_output
49
+ end
50
+
51
+ def remove_preexisting_test_result_bundles
52
+ return unless @options[:result_bundle]
53
+
54
+ absolute_output_directory = File.absolute_path(@options[:output_directory])
55
+ glob_pattern = "#{absolute_output_directory}/*.test_result"
56
+ preexisting_test_result_bundles = Dir.glob(glob_pattern)
57
+ FileUtils.rm_rf(preexisting_test_result_bundles)
58
+ end
59
+
60
+ def scan_options
61
+ valid_scan_keys = Fastlane::Actions::ScanAction.available_options.map(&:key)
62
+ @options.select { |k,v| valid_scan_keys.include?(k) }
63
+ .merge(@reportnamer.scan_options)
64
+ end
65
+
66
+ # after_testrun methods
67
+
68
+ def after_testrun(exception = nil)
69
+ @testrun_count = @testrun_count + 1
70
+ if exception.kind_of?(FastlaneCore::Interface::FastlaneTestFailure)
71
+ handle_test_failure
72
+ elsif exception.kind_of?(FastlaneCore::Interface::FastlaneBuildFailure)
73
+ handle_build_failure(exception)
74
+ else
75
+ handle_success
76
+ end
77
+ end
78
+
79
+ def handle_success
80
+ send_callback_testrun_info
81
+ move_test_result_bundle_for_next_run
82
+ reset_json_env
83
+ collate_reports
84
+ end
85
+
86
+ def collate_reports
87
+ absolute_output_directory = File.absolute_path(@options[:output_directory])
88
+
89
+ TestCenter::Helper::MultiScanManager::ReportCollator.new(
90
+ source_reports_directory_glob: absolute_output_directory,
91
+ output_directory: absolute_output_directory,
92
+ reportnamer: @reportnamer,
93
+ scheme: @options[:scheme],
94
+ result_bundle: @options[:result_bundle]
95
+ ).collate
96
+ end
97
+
98
+ def handle_test_failure
99
+ send_callback_testrun_info
100
+ reset_simulators
101
+ move_test_result_bundle_for_next_run
102
+ update_scan_options
103
+ @reportnamer.increment
104
+ end
105
+
106
+ def send_callback_testrun_info(additional_info = {})
107
+ return unless @options[:testrun_completed_block]
108
+
109
+ report_filepath = nil
110
+ junit_results = {}
111
+ unless additional_info.key?(:test_operation_failure)
112
+ report_filepath = File.absolute_path(File.join(@options[:output_directory], @reportnamer.junit_last_reportname))
113
+
114
+ config = FastlaneCore::Configuration.create(
115
+ Fastlane::Actions::TestsFromJunitAction.available_options,
116
+ {
117
+ junit: File.absolute_path(report_filepath)
118
+ }
119
+ )
120
+ junit_results = Fastlane::Actions::TestsFromJunitAction.run(config)
121
+ end
122
+
123
+ info = {
124
+ failed: junit_results[:failed],
125
+ passing: junit_results[:passing],
126
+ batch: 1,
127
+ try_count: @testrun_count,
128
+ report_filepath: report_filepath
129
+ }.merge(additional_info)
130
+
131
+ if @reportnamer.includes_html?
132
+ html_report_filepath = File.join(@options[:output_directory], @reportnamer.html_last_reportname)
133
+ info[:html_report_filepath] = html_report_filepath
134
+ end
135
+ if @reportnamer.includes_json?
136
+ json_report_filepath = File.join(@options[:output_directory], @reportnamer.json_last_reportname)
137
+ info[:json_report_filepath] = json_report_filepath
138
+ end
139
+ if @options[:result_bundle]
140
+ test_result_suffix = '.test_result'
141
+ test_result_suffix.prepend("-#{@reportnamer.report_count}") unless @reportnamer.report_count.zero?
142
+ test_result_bundlepath = File.join(@options[:output_directory], @options[:scheme]) + test_result_suffix
143
+ info[:test_result_bundlepath] = test_result_bundlepath
144
+ end
145
+ @options[:testrun_completed_block].call(info)
146
+ end
147
+
148
+ def update_scan_options
149
+ update_only_testing
150
+ turn_off_code_coverage
151
+ end
152
+
153
+ def turn_off_code_coverage
154
+ # Turn off code coverage as code coverage reports are not merged and
155
+ # the first, more valuable, report will be overwritten
156
+ @options.delete(:code_coverage)
157
+ end
158
+
159
+ def update_only_testing
160
+ report_filepath = File.join(@options[:output_directory], @reportnamer.junit_last_reportname)
161
+ config = FastlaneCore::Configuration.create(
162
+ Fastlane::Actions::TestsFromJunitAction.available_options,
163
+ {
164
+ junit: File.absolute_path(report_filepath)
165
+ }
166
+ )
167
+ @options[:only_testing] = Fastlane::Actions::TestsFromJunitAction.run(config)[:failed]
168
+ end
169
+
170
+ def reset_simulators
171
+ return unless @options[:reset_simulators]
172
+
173
+ @options[:simulators].each(&:reset)
174
+ end
175
+
176
+ def handle_build_failure(exception)
177
+ test_operation_failure = ''
178
+
179
+ test_session_last_messages = last_lines_of_test_session_log
180
+ test_operation_failure_match = /Test operation failure: (?<test_operation_failure>.*)$/ =~ test_session_last_messages
181
+ if test_operation_failure_match.nil?
182
+ test_operation_failure = 'Unknown test operation failure'
183
+ end
184
+
185
+ case test_operation_failure
186
+ when /Test runner exited before starting test execution/
187
+ FastlaneCore::UI.error(test_operation_failure)
188
+ when /Lost connection to testmanagerd/
189
+ FastlaneCore::UI.error(test_operation_failure)
190
+ FastlaneCore::UI.important("com.apple.CoreSimulator.CoreSimulatorService may have become corrupt, consider quitting it")
191
+ if @options[:quit_core_simulator_service]
192
+ Fastlane::Actions::RestartCoreSimulatorServiceAction.run
193
+ end
194
+ else
195
+ FastlaneCore::UI.error(test_operation_failure)
196
+ send_callback_testrun_info(test_operation_failure: test_operation_failure)
197
+ raise exception
198
+ end
199
+ if @options[:reset_simulators]
200
+ @options[:simulators].each do |simulator|
201
+ simulator.reset
202
+ end
203
+ end
204
+ send_callback_testrun_info(test_operation_failure: test_operation_failure)
205
+ end
206
+
207
+ def last_lines_of_test_session_log
208
+ derived_data_path = File.expand_path(@options[:derived_data_path])
209
+ test_session_logs = Dir.glob("#{derived_data_path}/Logs/Test/*.xcresult/*_Test/Diagnostics/**/Session-*.log")
210
+ test_session_logs.sort! { |logfile1, logfile2| File.mtime(logfile1) <=> File.mtime(logfile2) }
211
+ test_session = File.open(test_session_logs.last)
212
+ backwards_seek_offset = -1 * [1000, test_session.stat.size].min
213
+ test_session.seek(backwards_seek_offset, IO::SEEK_END)
214
+ test_session_last_messages = test_session.read
215
+ end
216
+
217
+ def move_test_result_bundle_for_next_run
218
+ return unless @options[:result_bundle]
219
+
220
+ absolute_output_directory = File.absolute_path(@options[:output_directory])
221
+ glob_pattern = "#{absolute_output_directory}/*.test_result"
222
+ preexisting_test_result_bundles = Dir.glob(glob_pattern)
223
+ unnumbered_test_result_bundles = preexisting_test_result_bundles.reject do |test_result|
224
+ test_result =~ /.*-\d+\.test_result/
225
+ end
226
+ src_test_bundle = unnumbered_test_result_bundles.first
227
+ dst_test_bundle_parent_dir = File.dirname(src_test_bundle)
228
+ dst_test_bundle_basename = File.basename(src_test_bundle, '.test_result')
229
+ dst_test_bundle = "#{dst_test_bundle_parent_dir}/#{dst_test_bundle_basename}-#{@testrun_count}.test_result"
230
+ FileUtils.mkdir_p(dst_test_bundle)
231
+ FileUtils.mv(src_test_bundle, dst_test_bundle)
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,272 @@
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_relative './simulator_manager'
10
+
11
+ class Runner
12
+ Parallelization = TestCenter::Helper::MultiScanManager::Parallelization
13
+
14
+ attr_reader :retry_total_count
15
+
16
+ def initialize(multi_scan_options)
17
+ @output_directory = multi_scan_options[:output_directory] || 'test_results'
18
+ @try_count = multi_scan_options[:try_count]
19
+ @retry_total_count = 0
20
+ @testrun_completed_block = multi_scan_options[:testrun_completed_block]
21
+ @given_custom_report_file_name = multi_scan_options[:custom_report_file_name]
22
+ @given_output_types = multi_scan_options[:output_types]
23
+ @given_output_files = multi_scan_options[:output_files]
24
+ @parallelize = multi_scan_options[:parallelize]
25
+ @test_collector = TestCenter::Helper::TestCollector.new(multi_scan_options)
26
+ @scan_options = multi_scan_options.reject do |option, _|
27
+ %i[
28
+ output_directory
29
+ only_testing
30
+ skip_testing
31
+ clean
32
+ try_count
33
+ batch_count
34
+ custom_report_file_name
35
+ fail_build
36
+ testrun_completed_block
37
+ output_types
38
+ output_files
39
+ parallelize
40
+ quit_simulators
41
+ ].include?(option)
42
+ end
43
+ @scan_options[:clean] = false
44
+ @scan_options[:disable_concurrent_testing] = true
45
+ @scan_options[:xctestrun] = @test_collector.xctestrun_path
46
+ @batch_count = @test_collector.test_batches.size
47
+ if @parallelize
48
+ @scan_options.delete(:derived_data_path)
49
+ @parallelizer = Parallelization.new(@batch_count, @output_directory, @testrun_completed_block)
50
+ end
51
+ end
52
+
53
+ def scan
54
+ all_tests_passed = true
55
+ @testables_count = @test_collector.testables.size
56
+ all_tests_passed = each_batch do |test_batch, current_batch_index|
57
+ if ENV['USE_REFACTORED_PARALLELIZED_MULTI_SCAN']
58
+ retrying_scan = TestCenter::Helper::MultiScanManager::RetryingScan.new(
59
+ @scan_options.merge(
60
+ only_testing: test_batch.map(&:shellsafe_testidentifier),
61
+ output_directory: @output_directory,
62
+ destination: @parallelizer&.destination_for_batch(current_batch_index) || Scan.config[:destination]
63
+ ).reject { |key| %i[device devices].include?(key) }
64
+ )
65
+ retrying_scan.run
66
+ else
67
+ output_directory = testrun_output_directory(@output_directory, test_batch, current_batch_index)
68
+ reset_for_new_testable(output_directory)
69
+ FastlaneCore::UI.header("Starting test run on batch '#{current_batch_index}'")
70
+ @interstitial.batch = current_batch_index
71
+ @interstitial.output_directory = output_directory
72
+ @interstitial.before_all
73
+ testrun_passed = correcting_scan(
74
+ {
75
+ only_testing: test_batch.map(&:shellsafe_testidentifier),
76
+ output_directory: output_directory
77
+ },
78
+ current_batch_index,
79
+ @reportnamer
80
+ )
81
+ all_tests_passed = testrun_passed && all_tests_passed
82
+ TestCenter::Helper::MultiScanManager::ReportCollator.new(
83
+ source_reports_directory_glob: output_directory,
84
+ output_directory: output_directory,
85
+ reportnamer: @reportnamer,
86
+ scheme: @scan_options[:scheme],
87
+ result_bundle: @scan_options[:result_bundle]
88
+ ).collate
89
+ end
90
+ testrun_passed && all_tests_passed
91
+ end
92
+ all_tests_passed
93
+ end
94
+
95
+ def each_batch
96
+ tests_passed = true
97
+ if @parallelize
98
+ xctestrun_filename = File.basename(@test_collector.xctestrun_path)
99
+ xcproduct_dirpath = File.dirname(@test_collector.xctestrun_path)
100
+ tmp_xcproduct_dirpath = Dir.mktmpdir
101
+
102
+ FileUtils.copy_entry(xcproduct_dirpath, tmp_xcproduct_dirpath)
103
+
104
+ tmp_xctestrun_path = File.join(tmp_xcproduct_dirpath, xctestrun_filename)
105
+ app_infoplist = XCTestrunInfo.new(tmp_xctestrun_path)
106
+ @scan_options[:xctestrun] = tmp_xctestrun_path
107
+ batch_deploymentversions = @test_collector.test_batches.map do |test_batch|
108
+ testable = test_batch.first.split('/').first.gsub('\\', '')
109
+ # TODO: investigate the reason for this call that doesn't seem to do
110
+ # anything other than query for and then discard MinimumOSVersion
111
+ app_infoplist.app_plist_for_testable(testable)['MinimumOSVersion']
112
+ end
113
+ @parallelizer.setup_simulators(@scan_options[:devices] || Array(@scan_options[:device]), batch_deploymentversions)
114
+ @parallelizer.setup_pipes_for_fork
115
+ @test_collector.test_batches.each_with_index do |test_batch, current_batch_index|
116
+ fork do
117
+ @parallelizer.connect_subprocess_endpoint(current_batch_index)
118
+ begin
119
+ @parallelizer.setup_scan_options_for_testrun(@scan_options, current_batch_index)
120
+ # add output_directory to map of test-target: [ output_directories ]
121
+ tests_passed = yield(test_batch, current_batch_index)
122
+ ensure
123
+ @parallelizer.send_subprocess_result(current_batch_index, tests_passed)
124
+ end
125
+ # processes to disconnect from the Simulator subsystems
126
+ FastlaneCore::UI.message("batched scan #{current_batch_index} finishing")
127
+ end
128
+ end
129
+ # @parallelizer.wait_for_subprocesses
130
+ # tests_passed = @parallelizer.handle_subprocesses_results && tests_passed
131
+ @parallelizer.handle_subprocesses
132
+ @parallelizer.cleanup_simulators
133
+ @test_collector.testables.each do |testable|
134
+ # ReportCollator with a testable-batch glob pattern
135
+ source_reports_directory_glob = batched_testable_output_directory(@output_directory, '*', testable)
136
+ @reportnamer = ReportNameHelper.new(
137
+ @given_output_types,
138
+ @given_output_files,
139
+ @given_custom_report_file_name
140
+ )
141
+ TestCenter::Helper::MultiScanManager::ReportCollator.new(
142
+ source_reports_directory_glob: source_reports_directory_glob,
143
+ output_directory: @output_directory,
144
+ reportnamer: @reportnamer,
145
+ scheme: @scan_options[:scheme],
146
+ result_bundle: @scan_options[:result_bundle],
147
+ suffix: testable
148
+ ).collate
149
+ FileUtils.rm_rf(Dir.glob(source_reports_directory_glob))
150
+ end
151
+ # for each key in test-target : [ output_directories ], call
152
+ # collate_junit_reports for each key, the [ output_directories ] being
153
+ # used to find the report files.
154
+ # the resultant report file is to be placed in the originally requested
155
+ # output_directory, with the name changed to include a suffix matching
156
+ # the test target's name
157
+ else
158
+ @test_collector.test_batches.each_with_index do |test_batch, current_batch_index|
159
+ tests_passed = yield(test_batch, current_batch_index)
160
+ end
161
+ end
162
+ tests_passed
163
+ end
164
+
165
+ def batched_testable_output_directory(output_directory, batch_index, testable_name)
166
+ File.join(output_directory, "results-#{testable_name}-batch-#{batch_index}")
167
+ end
168
+
169
+ def testrun_output_directory(base_output_directory, test_batch, batch_index)
170
+ return base_output_directory if @batch_count == 1
171
+
172
+ testable_name = test_batch.first.split('/').first.gsub(/\\/, '')
173
+ batched_testable_output_directory(base_output_directory, batch_index, testable_name)
174
+ end
175
+
176
+ def reset_reportnamer
177
+ @reportnamer = ReportNameHelper.new(
178
+ @given_output_types,
179
+ @given_output_files,
180
+ @given_custom_report_file_name
181
+ )
182
+ end
183
+
184
+ def test_run_completed_callback
185
+ if @parallelize && @testrun_completed_block
186
+ Proc.new do |info|
187
+ puts "about to call @parallelizer.send_subprocess_tryinfo(#{info})"
188
+ @parallelizer.send_subprocess_tryinfo(info)
189
+ end
190
+ else
191
+ @testrun_completed_block
192
+ end
193
+ end
194
+
195
+ def reset_interstitial(output_directory)
196
+ @interstitial = TestCenter::Helper::MultiScanManager::Interstitial.new(
197
+ @scan_options.merge(
198
+ {
199
+ output_directory: output_directory,
200
+ reportnamer: @reportnamer,
201
+ testrun_completed_block: @testrun_completed_block,
202
+ parallelize: @parallelize
203
+ }
204
+ )
205
+ )
206
+ end
207
+
208
+ def reset_for_new_testable(output_directory)
209
+ reset_reportnamer
210
+ reset_interstitial(output_directory)
211
+ end
212
+
213
+ def correcting_scan(scan_run_options, batch, reportnamer)
214
+ scan_options = @scan_options.merge(scan_run_options)
215
+ try_count = 0
216
+ tests_passed = true
217
+ begin
218
+ try_count += 1
219
+ config = FastlaneCore::Configuration.create(
220
+ Fastlane::Actions::ScanAction.available_options,
221
+ scan_options.merge(reportnamer.scan_options)
222
+ )
223
+ Fastlane::Actions::ScanAction.run(config)
224
+ @interstitial.finish_try(try_count)
225
+ tests_passed = true
226
+ rescue FastlaneCore::Interface::FastlaneTestFailure => e
227
+ FastlaneCore::UI.verbose("Scan failed with #{e}")
228
+ if try_count < @try_count
229
+ @retry_total_count += 1
230
+ scan_options.delete(:code_coverage)
231
+ tests_to_retry = failed_tests(reportnamer, scan_options[:output_directory])
232
+
233
+ scan_options[:only_testing] = tests_to_retry.map(&:shellsafe_testidentifier)
234
+ FastlaneCore::UI.message('Re-running scan on only failed tests')
235
+ @interstitial.finish_try(try_count)
236
+ retry
237
+ end
238
+ tests_passed = false
239
+ end
240
+ tests_passed
241
+ end
242
+
243
+ def failed_tests(reportnamer, output_directory)
244
+ report_filepath = File.join(output_directory, reportnamer.junit_last_reportname)
245
+ config = FastlaneCore::Configuration.create(
246
+ Fastlane::Actions::TestsFromJunitAction.available_options,
247
+ {
248
+ junit: File.absolute_path(report_filepath)
249
+ }
250
+ )
251
+ Fastlane::Actions::TestsFromJunitAction.run(config)[:failed]
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+
259
+ module FastlaneCore
260
+ class Shell < Interface
261
+ def format_string(datetime = Time.now, severity = "")
262
+ prefix = $batch_index.nil? ? '' : "#{$batch_index}: "
263
+ if FastlaneCore::Globals.verbose?
264
+ return "#{prefix}#{severity} [#{datetime.strftime('%Y-%m-%d %H:%M:%S.%2N')}]: "
265
+ elsif FastlaneCore::Env.truthy?("FASTLANE_HIDE_TIMESTAMP")
266
+ return prefix
267
+ else
268
+ return "#{prefix}[#{datetime.strftime('%H:%M:%S')}]: "
269
+ end
270
+ end
271
+ end
272
+ end