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.
- checksums.yaml +5 -5
- data/lib/fastlane/plugin/test_center.rb +1 -1
- data/lib/fastlane/plugin/test_center/actions/collate_test_result_bundles.rb +1 -1
- data/lib/fastlane/plugin/test_center/actions/multi_scan.rb +28 -4
- data/lib/fastlane/plugin/test_center/actions/restart_core_simulator_service.rb +37 -0
- data/lib/fastlane/plugin/test_center/actions/tests_from_xctestrun.rb +1 -1
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager.rb +5 -0
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/device_manager.rb +26 -0
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/interstitial.rb +143 -0
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/report_collator.rb +113 -0
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan.rb +72 -0
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb +236 -0
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb +272 -0
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/simulator_helper.rb +59 -0
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/simulator_manager.rb +317 -0
- data/lib/fastlane/plugin/test_center/helper/reportname_helper.rb +15 -6
- data/lib/fastlane/plugin/test_center/helper/test_collector.rb +47 -3
- data/lib/fastlane/plugin/test_center/helper/xcodebuild_string.rb +9 -0
- data/lib/fastlane/plugin/test_center/helper/xctestrun_info.rb +42 -0
- data/lib/fastlane/plugin/test_center/version.rb +1 -1
- metadata +21 -12
- 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
|