fastlane-plugin-test_center 3.6.2 → 3.6.3.parallelizing

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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