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.
- 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
|