fastlane-plugin-test_center 3.7.0.parallelizing.alpha.4 → 3.7.0
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 +4 -4
- 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 +34 -89
- data/lib/fastlane/plugin/test_center/helper/correcting_scan_helper.rb +315 -0
- data/lib/fastlane/plugin/test_center/helper/reportname_helper.rb +6 -15
- data/lib/fastlane/plugin/test_center/helper/test_collector.rb +11 -48
- data/lib/fastlane/plugin/test_center/version.rb +1 -1
- metadata +11 -24
- data/lib/fastlane/plugin/test_center/actions/restart_core_simulator_service.rb +0 -38
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager.rb +0 -5
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/device_manager.rb +0 -30
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/interstitial.rb +0 -143
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/parallel_test_batch_worker.rb +0 -27
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/report_collator.rb +0 -115
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan.rb +0 -74
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb +0 -255
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb +0 -356
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/simulator_helper.rb +0 -49
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/simulator_manager.rb +0 -317
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/test_batch_worker.rb +0 -20
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/test_batch_worker_pool.rb +0 -129
- data/lib/fastlane/plugin/test_center/helper/xctestrun_info.rb +0 -42
@@ -1,356 +0,0 @@
|
|
1
|
-
|
2
|
-
module TestCenter
|
3
|
-
module Helper
|
4
|
-
module MultiScanManager
|
5
|
-
require 'fastlane_core/ui/ui.rb'
|
6
|
-
require 'plist'
|
7
|
-
require 'json'
|
8
|
-
require 'shellwords'
|
9
|
-
require 'snapshot/reset_simulators'
|
10
|
-
require_relative './simulator_manager'
|
11
|
-
|
12
|
-
class Runner
|
13
|
-
Parallelization = TestCenter::Helper::MultiScanManager::Parallelization
|
14
|
-
|
15
|
-
attr_reader :retry_total_count
|
16
|
-
|
17
|
-
def initialize(multi_scan_options)
|
18
|
-
@output_directory = multi_scan_options[:output_directory] || 'test_results'
|
19
|
-
@try_count = multi_scan_options[:try_count]
|
20
|
-
@retry_total_count = 0
|
21
|
-
@testrun_completed_block = multi_scan_options[:testrun_completed_block]
|
22
|
-
@given_custom_report_file_name = multi_scan_options[:custom_report_file_name]
|
23
|
-
@given_output_types = multi_scan_options[:output_types]
|
24
|
-
@given_output_files = multi_scan_options[:output_files]
|
25
|
-
@invocation_based_tests = multi_scan_options[:invocation_based_tests]
|
26
|
-
@parallelize = multi_scan_options[:parallelize]
|
27
|
-
if ENV['USE_REFACTORED_PARALLELIZED_MULTI_SCAN']
|
28
|
-
@test_collector = TestCenter::Helper::TestCollector.new(multi_scan_options)
|
29
|
-
@scan_options = multi_scan_options
|
30
|
-
else
|
31
|
-
@test_collector = TestCenter::Helper::TestCollector.new(multi_scan_options)
|
32
|
-
@scan_options = multi_scan_options.reject do |option, _|
|
33
|
-
%i[
|
34
|
-
output_directory
|
35
|
-
only_testing
|
36
|
-
skip_testing
|
37
|
-
clean
|
38
|
-
try_count
|
39
|
-
batch_count
|
40
|
-
custom_report_file_name
|
41
|
-
fail_build
|
42
|
-
testrun_completed_block
|
43
|
-
output_types
|
44
|
-
output_files
|
45
|
-
parallelize
|
46
|
-
quit_simulators
|
47
|
-
invocation_based_tests
|
48
|
-
].include?(option)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
@scan_options[:clean] = false
|
52
|
-
@scan_options[:disable_concurrent_testing] = true
|
53
|
-
@scan_options[:xctestrun] = @test_collector.xctestrun_path
|
54
|
-
@batch_count = @test_collector.test_batches.size
|
55
|
-
if @parallelize
|
56
|
-
@scan_options.delete(:derived_data_path)
|
57
|
-
@parallelizer = Parallelization.new(@batch_count, @output_directory, @testrun_completed_block)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def run
|
62
|
-
if @invocation_based_tests
|
63
|
-
run_invocation_based_tests
|
64
|
-
else
|
65
|
-
run_test_batches
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def run_invocation_based_tests
|
70
|
-
@scan_options[:only_testing] = @scan_options[:only_testing]&.map(&:strip_testcase)&.uniq
|
71
|
-
@scan_options[:skip_testing] = @scan_options[:skip_testing]&.map(&:strip_testcase)&.uniq
|
72
|
-
|
73
|
-
RetryingScan.run(@scan_options)
|
74
|
-
end
|
75
|
-
|
76
|
-
def run_test_batches
|
77
|
-
all_tests_passed = true
|
78
|
-
pool_options = @scan_options.reject { |key| %i[device devices].include?(key) }
|
79
|
-
pool = TestBatchWorkerPool.new(pool_options)
|
80
|
-
pool.setup_workers
|
81
|
-
|
82
|
-
remaining_test_batches = @test_collector.test_batches.clone
|
83
|
-
remaining_test_batches.each_with_index do |test_batch, current_batch_index|
|
84
|
-
worker = pool.wait_for_worker
|
85
|
-
|
86
|
-
testrun_passed = worker.run(
|
87
|
-
{
|
88
|
-
only_testing: test_batch.map(&:shellsafe_testidentifier),
|
89
|
-
output_directory: @output_directory,
|
90
|
-
try_count: @try_count,
|
91
|
-
batch: current_batch_index + 1
|
92
|
-
}
|
93
|
-
)
|
94
|
-
all_tests_passed = testrun_passed && all_tests_passed
|
95
|
-
end
|
96
|
-
pool.wait_for_all_workers
|
97
|
-
collate_batched_reports
|
98
|
-
all_tests_passed
|
99
|
-
end
|
100
|
-
|
101
|
-
def scan
|
102
|
-
return run if ENV['USE_REFACTORED_PARALLELIZED_MULTI_SCAN']
|
103
|
-
|
104
|
-
all_tests_passed = true
|
105
|
-
@testables_count = @test_collector.testables.size
|
106
|
-
FileUtils.rm_rf(Dir.glob("#{@output_directory}/**/*.{junit,html,xml,json}"))
|
107
|
-
all_tests_passed = each_batch do |test_batch, current_batch_index|
|
108
|
-
output_directory = testrun_output_directory(@output_directory, test_batch, current_batch_index)
|
109
|
-
reset_for_new_testable(output_directory)
|
110
|
-
FastlaneCore::UI.header("Starting test run on batch '#{current_batch_index}'")
|
111
|
-
@interstitial.batch = current_batch_index
|
112
|
-
@interstitial.output_directory = output_directory
|
113
|
-
@interstitial.before_all
|
114
|
-
testrun_passed = correcting_scan(
|
115
|
-
{
|
116
|
-
only_testing: test_batch.map(&:shellsafe_testidentifier),
|
117
|
-
output_directory: output_directory
|
118
|
-
},
|
119
|
-
current_batch_index,
|
120
|
-
@reportnamer
|
121
|
-
)
|
122
|
-
all_tests_passed = testrun_passed && all_tests_passed
|
123
|
-
TestCenter::Helper::MultiScanManager::ReportCollator.new(
|
124
|
-
source_reports_directory_glob: output_directory,
|
125
|
-
output_directory: output_directory,
|
126
|
-
reportnamer: @reportnamer,
|
127
|
-
scheme: @scan_options[:scheme],
|
128
|
-
result_bundle: @scan_options[:result_bundle]
|
129
|
-
).collate
|
130
|
-
testrun_passed && all_tests_passed
|
131
|
-
end
|
132
|
-
all_tests_passed
|
133
|
-
end
|
134
|
-
|
135
|
-
def run_test_batch_through_retrying_scan(test_batch, current_batch_index)
|
136
|
-
FastlaneCore::UI.important("Investigate using rbspy for perf problems: https://github.com/rbspy/rbspy")
|
137
|
-
retrying_scan = TestCenter::Helper::MultiScanManager::RetryingScan.new(
|
138
|
-
@scan_options.merge(
|
139
|
-
only_testing: test_batch.map(&:shellsafe_testidentifier),
|
140
|
-
output_directory: @output_directory,
|
141
|
-
try_count: @try_count,
|
142
|
-
batch: current_batch_index + 1
|
143
|
-
).reject { |key| %i[device devices].include?(key) }
|
144
|
-
)
|
145
|
-
retrying_scan.run
|
146
|
-
end
|
147
|
-
|
148
|
-
def collate_batched_reports
|
149
|
-
return false unless @batch_count > 1
|
150
|
-
|
151
|
-
@test_collector.testables.each do |testable|
|
152
|
-
collate_batched_reports_for_testable(testable)
|
153
|
-
end
|
154
|
-
true
|
155
|
-
end
|
156
|
-
|
157
|
-
def collate_batched_reports_for_testable(testable)
|
158
|
-
absolute_output_directory = File.join(
|
159
|
-
File.absolute_path(@output_directory),
|
160
|
-
testable
|
161
|
-
)
|
162
|
-
source_reports_directory_glob = "#{absolute_output_directory}-batch-*"
|
163
|
-
|
164
|
-
TestCenter::Helper::MultiScanManager::ReportCollator.new(
|
165
|
-
source_reports_directory_glob: source_reports_directory_glob,
|
166
|
-
output_directory: absolute_output_directory,
|
167
|
-
reportnamer: @reportnamer = ReportNameHelper.new(
|
168
|
-
@given_output_types,
|
169
|
-
@given_output_files,
|
170
|
-
@given_custom_report_file_name
|
171
|
-
),
|
172
|
-
scheme: @scan_options[:scheme],
|
173
|
-
result_bundle: @scan_options[:result_bundle]
|
174
|
-
).collate
|
175
|
-
FileUtils.rm_rf(Dir.glob(source_reports_directory_glob))
|
176
|
-
true
|
177
|
-
end
|
178
|
-
|
179
|
-
def each_batch
|
180
|
-
tests_passed = true
|
181
|
-
if @parallelize
|
182
|
-
xctestrun_filename = File.basename(@test_collector.xctestrun_path)
|
183
|
-
xcproduct_dirpath = File.dirname(@test_collector.xctestrun_path)
|
184
|
-
tmp_xcproduct_dirpath = Dir.mktmpdir
|
185
|
-
|
186
|
-
FileUtils.copy_entry(xcproduct_dirpath, tmp_xcproduct_dirpath)
|
187
|
-
|
188
|
-
tmp_xctestrun_path = File.join(tmp_xcproduct_dirpath, xctestrun_filename)
|
189
|
-
app_infoplist = XCTestrunInfo.new(tmp_xctestrun_path)
|
190
|
-
@scan_options[:xctestrun] = tmp_xctestrun_path
|
191
|
-
batch_deploymentversions = @test_collector.test_batches.map do |test_batch|
|
192
|
-
testable = test_batch.first.split('/').first.gsub('\\', '')
|
193
|
-
# TODO: investigate the reason for this call that doesn't seem to do
|
194
|
-
# anything other than query for and then discard MinimumOSVersion
|
195
|
-
app_infoplist.app_plist_for_testable(testable)['MinimumOSVersion']
|
196
|
-
end
|
197
|
-
@parallelizer.setup_simulators(@scan_options[:devices] || Array(@scan_options[:device]), batch_deploymentversions)
|
198
|
-
@parallelizer.setup_pipes_for_fork
|
199
|
-
@test_collector.test_batches.each_with_index do |test_batch, current_batch_index|
|
200
|
-
fork do
|
201
|
-
@parallelizer.connect_subprocess_endpoint(current_batch_index)
|
202
|
-
begin
|
203
|
-
@parallelizer.setup_scan_options_for_testrun(@scan_options, current_batch_index)
|
204
|
-
# add output_directory to map of test-target: [ output_directories ]
|
205
|
-
tests_passed = yield(test_batch, current_batch_index)
|
206
|
-
ensure
|
207
|
-
@parallelizer.send_subprocess_result(current_batch_index, tests_passed)
|
208
|
-
end
|
209
|
-
# processes to disconnect from the Simulator subsystems
|
210
|
-
FastlaneCore::UI.message("batched scan #{current_batch_index} finishing")
|
211
|
-
end
|
212
|
-
end
|
213
|
-
# @parallelizer.wait_for_subprocesses
|
214
|
-
# tests_passed = @parallelizer.handle_subprocesses_results && tests_passed
|
215
|
-
@parallelizer.handle_subprocesses
|
216
|
-
@parallelizer.cleanup_simulators
|
217
|
-
@test_collector.testables.each do |testable|
|
218
|
-
# ReportCollator with a testable-batch glob pattern
|
219
|
-
source_reports_directory_glob = batched_testable_output_directory(@output_directory, '*', testable)
|
220
|
-
@reportnamer = ReportNameHelper.new(
|
221
|
-
@given_output_types,
|
222
|
-
@given_output_files,
|
223
|
-
@given_custom_report_file_name
|
224
|
-
)
|
225
|
-
TestCenter::Helper::MultiScanManager::ReportCollator.new(
|
226
|
-
source_reports_directory_glob: source_reports_directory_glob,
|
227
|
-
output_directory: @output_directory,
|
228
|
-
reportnamer: @reportnamer,
|
229
|
-
scheme: @scan_options[:scheme],
|
230
|
-
result_bundle: @scan_options[:result_bundle],
|
231
|
-
suffix: testable
|
232
|
-
).collate
|
233
|
-
FileUtils.rm_rf(Dir.glob(source_reports_directory_glob))
|
234
|
-
end
|
235
|
-
# for each key in test-target : [ output_directories ], call
|
236
|
-
# collate_junit_reports for each key, the [ output_directories ] being
|
237
|
-
# used to find the report files.
|
238
|
-
# the resultant report file is to be placed in the originally requested
|
239
|
-
# output_directory, with the name changed to include a suffix matching
|
240
|
-
# the test target's name
|
241
|
-
else
|
242
|
-
@test_collector.test_batches.each_with_index do |test_batch, current_batch_index|
|
243
|
-
tests_passed = yield(test_batch, current_batch_index)
|
244
|
-
end
|
245
|
-
end
|
246
|
-
tests_passed
|
247
|
-
end
|
248
|
-
|
249
|
-
def batched_testable_output_directory(output_directory, batch_index, testable_name)
|
250
|
-
File.join(output_directory, "results-#{testable_name}-batch-#{batch_index}")
|
251
|
-
end
|
252
|
-
|
253
|
-
def testrun_output_directory(base_output_directory, test_batch, batch_index)
|
254
|
-
return base_output_directory if @batch_count == 1
|
255
|
-
|
256
|
-
testable_name = test_batch.first.split('/').first.gsub(/\\/, '')
|
257
|
-
batched_testable_output_directory(base_output_directory, batch_index, testable_name)
|
258
|
-
end
|
259
|
-
|
260
|
-
def reset_reportnamer
|
261
|
-
@reportnamer = ReportNameHelper.new(
|
262
|
-
@given_output_types,
|
263
|
-
@given_output_files,
|
264
|
-
@given_custom_report_file_name
|
265
|
-
)
|
266
|
-
end
|
267
|
-
|
268
|
-
def test_run_completed_callback
|
269
|
-
if @parallelize && @testrun_completed_block
|
270
|
-
Proc.new do |info|
|
271
|
-
puts "about to call @parallelizer.send_subprocess_tryinfo(#{info})"
|
272
|
-
@parallelizer.send_subprocess_tryinfo(info)
|
273
|
-
end
|
274
|
-
else
|
275
|
-
@testrun_completed_block
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
def reset_interstitial(output_directory)
|
280
|
-
@interstitial = TestCenter::Helper::MultiScanManager::Interstitial.new(
|
281
|
-
@scan_options.merge(
|
282
|
-
{
|
283
|
-
output_directory: output_directory,
|
284
|
-
reportnamer: @reportnamer,
|
285
|
-
testrun_completed_block: @testrun_completed_block,
|
286
|
-
parallelize: @parallelize
|
287
|
-
}
|
288
|
-
)
|
289
|
-
)
|
290
|
-
end
|
291
|
-
|
292
|
-
def reset_for_new_testable(output_directory)
|
293
|
-
reset_reportnamer
|
294
|
-
reset_interstitial(output_directory)
|
295
|
-
end
|
296
|
-
|
297
|
-
def correcting_scan(scan_run_options, batch, reportnamer)
|
298
|
-
scan_options = @scan_options.merge(scan_run_options)
|
299
|
-
try_count = 0
|
300
|
-
tests_passed = true
|
301
|
-
begin
|
302
|
-
try_count += 1
|
303
|
-
config = FastlaneCore::Configuration.create(
|
304
|
-
Fastlane::Actions::ScanAction.available_options,
|
305
|
-
scan_options.merge(reportnamer.scan_options)
|
306
|
-
)
|
307
|
-
Fastlane::Actions::ScanAction.run(config)
|
308
|
-
@interstitial.finish_try(try_count)
|
309
|
-
tests_passed = true
|
310
|
-
rescue FastlaneCore::Interface::FastlaneTestFailure => e
|
311
|
-
FastlaneCore::UI.verbose("Scan failed with #{e}")
|
312
|
-
if try_count < @try_count
|
313
|
-
@retry_total_count += 1
|
314
|
-
scan_options.delete(:code_coverage)
|
315
|
-
tests_to_retry = failed_tests(reportnamer, scan_options[:output_directory])
|
316
|
-
|
317
|
-
scan_options[:only_testing] = tests_to_retry.map(&:shellsafe_testidentifier)
|
318
|
-
FastlaneCore::UI.message('Re-running scan on only failed tests')
|
319
|
-
@interstitial.finish_try(try_count)
|
320
|
-
retry
|
321
|
-
end
|
322
|
-
tests_passed = false
|
323
|
-
end
|
324
|
-
tests_passed
|
325
|
-
end
|
326
|
-
|
327
|
-
def failed_tests(reportnamer, output_directory)
|
328
|
-
report_filepath = File.join(output_directory, reportnamer.junit_last_reportname)
|
329
|
-
config = FastlaneCore::Configuration.create(
|
330
|
-
Fastlane::Actions::TestsFromJunitAction.available_options,
|
331
|
-
{
|
332
|
-
junit: File.absolute_path(report_filepath)
|
333
|
-
}
|
334
|
-
)
|
335
|
-
Fastlane::Actions::TestsFromJunitAction.run(config)[:failed]
|
336
|
-
end
|
337
|
-
end
|
338
|
-
end
|
339
|
-
end
|
340
|
-
end
|
341
|
-
|
342
|
-
|
343
|
-
module FastlaneCore
|
344
|
-
class Shell < Interface
|
345
|
-
def format_string(datetime = Time.now, severity = "")
|
346
|
-
prefix = $batch_index.nil? ? '' : "#{$batch_index}: "
|
347
|
-
if FastlaneCore::Globals.verbose?
|
348
|
-
return "#{prefix}#{severity} [#{datetime.strftime('%Y-%m-%d %H:%M:%S.%2N')}]: "
|
349
|
-
elsif FastlaneCore::Env.truthy?("FASTLANE_HIDE_TIMESTAMP")
|
350
|
-
return prefix
|
351
|
-
else
|
352
|
-
return "#{prefix}[#{datetime.strftime('%H:%M:%S')}]: "
|
353
|
-
end
|
354
|
-
end
|
355
|
-
end
|
356
|
-
end
|
@@ -1,49 +0,0 @@
|
|
1
|
-
module TestCenter
|
2
|
-
module Helper
|
3
|
-
module MultiScanManager
|
4
|
-
class SimulatorHelper
|
5
|
-
def initialize(options)
|
6
|
-
@options = options
|
7
|
-
end
|
8
|
-
|
9
|
-
def setup
|
10
|
-
if (@options[:batch_count] || @options[:parallel_simulator_fork_count] || 1) > 1
|
11
|
-
delete_multi_scan_cloned_simulators
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def clone_destination_simulators
|
16
|
-
cloned_simulators = []
|
17
|
-
|
18
|
-
batch_count = @options[:batch_count] || @options[:parallel_simulator_fork_count] || 0
|
19
|
-
destination_simulator_ids = Scan.config[:destination].map do |destination|
|
20
|
-
destination.split(',id=').last
|
21
|
-
end
|
22
|
-
original_simulators = FastlaneCore::DeviceManager.simulators('iOS').find_all do |simulator|
|
23
|
-
destination_simulator_ids.include?(simulator.udid)
|
24
|
-
end
|
25
|
-
original_simulators.each(&:shutdown)
|
26
|
-
(0...batch_count).each do |batch_index|
|
27
|
-
cloned_simulators << []
|
28
|
-
original_simulators.each do |simulator|
|
29
|
-
FastlaneCore::UI.verbose("Cloning simulator")
|
30
|
-
cloned_simulator = simulator.clone
|
31
|
-
new_first_name = simulator.name.sub(/( ?\(.*| ?$)/, " Clone #{batch_index}")
|
32
|
-
new_last_name = "#{self.class.name}<#{self.object_id}>"
|
33
|
-
cloned_simulator.rename("#{new_first_name} #{new_last_name}")
|
34
|
-
|
35
|
-
cloned_simulators.last << cloned_simulator
|
36
|
-
end
|
37
|
-
end
|
38
|
-
cloned_simulators
|
39
|
-
end
|
40
|
-
|
41
|
-
def delete_multi_scan_cloned_simulators
|
42
|
-
FastlaneCore::DeviceManager.simulators('iOS').each do |simulator|
|
43
|
-
simulator.delete if /#{self.class.name}<\d+>/ =~ simulator.name
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
@@ -1,317 +0,0 @@
|
|
1
|
-
module TestCenter
|
2
|
-
module Helper
|
3
|
-
module MultiScanManager
|
4
|
-
require 'scan'
|
5
|
-
require 'colorize'
|
6
|
-
require_relative './device_manager'
|
7
|
-
|
8
|
-
class Parallelization
|
9
|
-
def initialize(batch_count, output_directory, testrun_completed_block)
|
10
|
-
@batch_count = batch_count
|
11
|
-
@output_directory = output_directory
|
12
|
-
@testrun_completed_block = testrun_completed_block
|
13
|
-
|
14
|
-
@simulators ||= []
|
15
|
-
|
16
|
-
if ENV['USE_REFACTORED_PARALLELIZED_MULTI_SCAN']
|
17
|
-
@simhelper = SimulatorHelper.new(
|
18
|
-
parallelize: true,
|
19
|
-
batch_count: batch_count
|
20
|
-
)
|
21
|
-
@simhelper.setup
|
22
|
-
end
|
23
|
-
if @batch_count < 1
|
24
|
-
raise FastlaneCore::FastlaneCrash.new({}), "batch_count (#{@batch_count}) < 1, this should never happen"
|
25
|
-
end
|
26
|
-
ObjectSpace.define_finalizer(self, self.class.finalize)
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.finalize
|
30
|
-
proc { cleanup_simulators }
|
31
|
-
end
|
32
|
-
|
33
|
-
def setup_simulators(devices, batch_deploymentversions)
|
34
|
-
if ENV['USE_REFACTORED_PARALLELIZED_MULTI_SCAN']
|
35
|
-
@simulators = @simhelper.clone_destination_simulators
|
36
|
-
else
|
37
|
-
FastlaneCore::DeviceManager.simulators('iOS').each do |simulator|
|
38
|
-
simulator.delete if /-batchclone-/ =~ simulator.name
|
39
|
-
end
|
40
|
-
|
41
|
-
(0...@batch_count).each do |batch_index|
|
42
|
-
found_simulator_devices = []
|
43
|
-
if devices.count > 0
|
44
|
-
found_simulator_devices = detect_simulator(devices, batch_deploymentversions[batch_index])
|
45
|
-
else
|
46
|
-
found_simulator_devices = Scan::DetectValues.detect_simulator(devices, 'iOS', 'IPHONEOS_DEPLOYMENT_TARGET', 'iPhone 5s', nil)
|
47
|
-
end
|
48
|
-
@simulators[batch_index] ||= []
|
49
|
-
found_simulator_devices.each do |found_simulator_device|
|
50
|
-
device_for_batch = found_simulator_device.clone
|
51
|
-
new_name = "#{found_simulator_device.name.gsub(/[^a-zA-Z\d]/,'')}-batchclone-#{batch_index + 1}"
|
52
|
-
device_for_batch.rename(new_name)
|
53
|
-
device_for_batch.boot
|
54
|
-
@simulators[batch_index] << device_for_batch
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def detect_simulator(devices, deployment_target_version)
|
61
|
-
require 'set'
|
62
|
-
|
63
|
-
simulators = Scan::DetectValues.filter_simulators(
|
64
|
-
FastlaneCore::DeviceManager.simulators('iOS').tap do |array|
|
65
|
-
if array.empty?
|
66
|
-
FastlaneCore::UI.user_error!(['No', simulator_type_descriptor, 'simulators found on local machine'].reject(&:nil?).join(' '))
|
67
|
-
end
|
68
|
-
end,
|
69
|
-
:greater_than_or_equal,
|
70
|
-
deployment_target_version
|
71
|
-
).tap do |sims|
|
72
|
-
if sims.empty?
|
73
|
-
FastlaneCore::UI.error("No simulators found that are greater than or equal to the version of deployment target (#{deployment_target_version})")
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# At this point we have all simulators for the given deployment target (or higher)
|
78
|
-
|
79
|
-
# We create 2 lambdas, which we iterate over later on
|
80
|
-
# If the first lambda `matches` found a simulator to use
|
81
|
-
# we'll never call the second one
|
82
|
-
|
83
|
-
matches = lambda do
|
84
|
-
set_of_simulators = devices.inject(
|
85
|
-
Set.new # of simulators
|
86
|
-
) do |set, device_string|
|
87
|
-
pieces = device_string.split(/\s(?=\([\d\.]+\)$)/)
|
88
|
-
|
89
|
-
selector = ->(sim) { pieces.count > 0 && sim.name == pieces.first }
|
90
|
-
|
91
|
-
set + (
|
92
|
-
if pieces.count == 0
|
93
|
-
[] # empty array
|
94
|
-
elsif pieces.count == 1
|
95
|
-
simulators
|
96
|
-
.select(&selector)
|
97
|
-
.reverse # more efficient, because `simctl` prints higher versions first
|
98
|
-
.sort_by! { |sim| Gem::Version.new(sim.os_version) }
|
99
|
-
.pop(1)
|
100
|
-
else # pieces.count == 2 -- mathematically, because of the 'end of line' part of our regular expression
|
101
|
-
version = pieces[1].tr('()', '')
|
102
|
-
potential_emptiness_error = lambda do |sims|
|
103
|
-
if sims.empty?
|
104
|
-
FastlaneCore::UI.error("No simulators found that are equal to the version " \
|
105
|
-
"of specifier (#{version}) and greater than or equal to the version " \
|
106
|
-
"of deployment target (#{deployment_target_version})")
|
107
|
-
end
|
108
|
-
end
|
109
|
-
Scan::DetectValues.filter_simulators(simulators, :equal, version).tap(&potential_emptiness_error).select(&selector)
|
110
|
-
end
|
111
|
-
).tap do |array|
|
112
|
-
FastlaneCore::UI.error("Ignoring '#{device_string}', couldn't find matching simulator") if array.empty?
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
set_of_simulators.to_a
|
117
|
-
end
|
118
|
-
|
119
|
-
default = lambda do
|
120
|
-
FastlaneCore::UI.error("Couldn't find any matching simulators for '#{devices}' - falling back to default simulator") if (devices || []).count > 0
|
121
|
-
|
122
|
-
result = Array(
|
123
|
-
simulators
|
124
|
-
.select { |sim| sim.name == default_device_name }
|
125
|
-
.reverse # more efficient, because `simctl` prints higher versions first
|
126
|
-
.sort_by! { |sim| Gem::Version.new(sim.os_version) }
|
127
|
-
.last || simulators.first
|
128
|
-
)
|
129
|
-
|
130
|
-
FastlaneCore::UI.message("Found simulator \"#{result.first.name} (#{result.first.os_version})\"") if result.first
|
131
|
-
|
132
|
-
result
|
133
|
-
end
|
134
|
-
|
135
|
-
[matches, default].lazy.map { |x|
|
136
|
-
arr = x.call
|
137
|
-
arr unless arr.empty?
|
138
|
-
}.reject(&:nil?).first
|
139
|
-
end
|
140
|
-
|
141
|
-
def cleanup_simulators
|
142
|
-
@simulators.flatten.each(&:delete)
|
143
|
-
@simulators = []
|
144
|
-
end
|
145
|
-
|
146
|
-
def destination_for_batch(batch_index)
|
147
|
-
@simulators[batch_index]
|
148
|
-
end
|
149
|
-
|
150
|
-
def devices(batch_index)
|
151
|
-
if batch_index > @batch_count
|
152
|
-
simulator_count = [@batch_count, @simulators.count].max
|
153
|
-
raise "Error: impossible to request devices for batch #{batch_index}, there are only #{simulator_count} set(s) of simulators"
|
154
|
-
end
|
155
|
-
|
156
|
-
if @simulators.count > 0
|
157
|
-
@simulators[batch_index - 1].map do |simulator|
|
158
|
-
"#{simulator.name} (#{simulator.os_version})"
|
159
|
-
end
|
160
|
-
else
|
161
|
-
@scan_options[:devices] || Array(@scan_options[:device])
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def ensure_conflict_free_scanlogging(scan_options, batch_index)
|
166
|
-
scan_options[:buildlog_path] = scan_options[:buildlog_path] + "-#{batch_index}"
|
167
|
-
end
|
168
|
-
|
169
|
-
def ensure_devices_cloned_for_testrun_are_used(scan_options, batch_index)
|
170
|
-
scan_options.delete(:device)
|
171
|
-
scan_options[:devices] = devices(batch_index)
|
172
|
-
end
|
173
|
-
|
174
|
-
def setup_scan_options_for_testrun(scan_options, batch_index)
|
175
|
-
ensure_conflict_free_scanlogging(scan_options, batch_index)
|
176
|
-
ensure_devices_cloned_for_testrun_are_used(scan_options, batch_index)
|
177
|
-
end
|
178
|
-
|
179
|
-
def setup_pipes_for_fork
|
180
|
-
@pipe_endpoints = []
|
181
|
-
(0...@batch_count).each do
|
182
|
-
@pipe_endpoints << IO.pipe
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
def connect_subprocess_endpoint(batch_index)
|
187
|
-
mainprocess_reader, = @pipe_endpoints[batch_index]
|
188
|
-
mainprocess_reader.close # we are now in the subprocess
|
189
|
-
FileUtils.mkdir_p(@output_directory)
|
190
|
-
subprocess_output_dir = Dir.mktmpdir
|
191
|
-
subprocess_logfilepath = File.join(subprocess_output_dir, "batchscan_#{batch_index}.log")
|
192
|
-
$subprocess_logfile = File.open(subprocess_logfilepath, 'w')
|
193
|
-
$subprocess_logfile.sync = true
|
194
|
-
$old_stdout = $stdout.dup
|
195
|
-
$old_stderr = $stderr.dup
|
196
|
-
$stdout.reopen($subprocess_logfile)
|
197
|
-
$stderr.reopen($subprocess_logfile)
|
198
|
-
end
|
199
|
-
|
200
|
-
def disconnect_subprocess_endpoints
|
201
|
-
# This is done from the parent process to close the pipe from its end so
|
202
|
-
# that its reading of the pipe doesn't block waiting for more IO on the
|
203
|
-
# writer.
|
204
|
-
# This has to be done after the fork, because we don't want the subprocess
|
205
|
-
# to receive its endpoint already closed.
|
206
|
-
@pipe_endpoints.each { |_, subprocess_writer| subprocess_writer.close }
|
207
|
-
end
|
208
|
-
|
209
|
-
def send_subprocess_tryinfo(info)
|
210
|
-
puts "in send_subprocess_tryinfo"
|
211
|
-
_, subprocess_writer = @pipe_endpoints[batch_index]
|
212
|
-
subprocess_output = {
|
213
|
-
'message_type' => 'tryinfo',
|
214
|
-
'batch_index' => batch_index,
|
215
|
-
'tryinfo' => info
|
216
|
-
}
|
217
|
-
subprocess_writer.puts subprocess_output.to_json
|
218
|
-
end
|
219
|
-
|
220
|
-
def send_subprocess_result(batch_index, result)
|
221
|
-
$stdout = $old_stdout.dup
|
222
|
-
$stderr = $old_stderr.dup
|
223
|
-
_, subprocess_writer = @pipe_endpoints[batch_index]
|
224
|
-
|
225
|
-
subprocess_output = {
|
226
|
-
'message_type' => 'completed',
|
227
|
-
'batch_index' => batch_index,
|
228
|
-
'subprocess_logfilepath' => $subprocess_logfile.path,
|
229
|
-
'tests_passed' => result
|
230
|
-
}
|
231
|
-
subprocess_writer.puts subprocess_output.to_json
|
232
|
-
subprocess_writer.close
|
233
|
-
$subprocess_logfile.close
|
234
|
-
end
|
235
|
-
|
236
|
-
def parse_subprocess_results(subprocess_index, subprocess_output)
|
237
|
-
subprocess_result = {
|
238
|
-
'tests_passed' => false
|
239
|
-
}
|
240
|
-
if subprocess_output.empty?
|
241
|
-
FastlaneCore::UI.error("Something went terribly wrong: no output from parallelized batch #{subprocess_index}!")
|
242
|
-
else
|
243
|
-
subprocess_result = JSON.parse(subprocess_output)
|
244
|
-
end
|
245
|
-
subprocess_result
|
246
|
-
end
|
247
|
-
|
248
|
-
def stream_subprocess_result_to_console(index, subprocess_logfilepath)
|
249
|
-
puts '-' * 80
|
250
|
-
if File.exist?(subprocess_logfilepath)
|
251
|
-
simulator_prefix = "[Sim-#{index}]"
|
252
|
-
unless ENV['FASTLANE_DISABLE_COLORS']
|
253
|
-
colors = String.colors - [:default, :black, :white]
|
254
|
-
color = colors.sample
|
255
|
-
simulator_prefix = simulator_prefix.colorize(color)
|
256
|
-
end
|
257
|
-
File.foreach(subprocess_logfilepath) do |line|
|
258
|
-
print simulator_prefix, line
|
259
|
-
end
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
def handle_subprocesses
|
264
|
-
# disconnect_subprocess_endpoints # to ensure no blocking on pipe
|
265
|
-
FastlaneCore::Helper.show_loading_indicator("Scanning in #{@batch_count} batches")
|
266
|
-
loop do
|
267
|
-
break if @pipe_endpoints.size.zero?
|
268
|
-
|
269
|
-
remaining_pipe_endpoints = @pipe_endpoints.map(&:first).reject { |pe| pe.closed? }
|
270
|
-
puts "there are #{remaining_pipe_endpoints.size} endpoints remaining".yellow
|
271
|
-
read_array, _, error_array = IO.select(remaining_pipe_endpoints)
|
272
|
-
read_array.each do |pipe_reader|
|
273
|
-
endpoint_index = @pipe_endpoints.find_index { |pe| pe.first == pipe_reader }
|
274
|
-
child_message_size = pipe_reader.stat.size
|
275
|
-
child_message = pipe_reader.read_nonblock(child_message_size)
|
276
|
-
subprocess_result = parse_subprocess_results(0, child_message)
|
277
|
-
if subprocess_result['message_type'] == 'completed'
|
278
|
-
stream_subprocess_result_to_console(subprocess_result['batch_index'], subprocess_result['subprocess_logfilepath'])
|
279
|
-
pipe_reader.close
|
280
|
-
@pipe_endpoints.delete_at(endpoint_index)
|
281
|
-
elsif subprocess_result['message_type'] == 'tryinfo'
|
282
|
-
puts "in simulator_manager, about to call @testrun_completed_block witth #{subprocess_result['tryinfo']}"
|
283
|
-
@testrun_completed_block && @testrun_completed_block.call(subprocess_result['tryinfo'])
|
284
|
-
end
|
285
|
-
end
|
286
|
-
error_array.each { |e| puts e }
|
287
|
-
break if error_array.size > 0
|
288
|
-
break if read_array.size.zero?
|
289
|
-
end
|
290
|
-
FastlaneCore::Helper.hide_loading_indicator
|
291
|
-
end
|
292
|
-
|
293
|
-
def wait_for_subprocesses
|
294
|
-
disconnect_subprocess_endpoints # to ensure no blocking on the pipe
|
295
|
-
FastlaneCore::Helper.show_loading_indicator("Scanning in #{@batch_count} batches")
|
296
|
-
Process.waitall
|
297
|
-
FastlaneCore::Helper.hide_loading_indicator
|
298
|
-
end
|
299
|
-
|
300
|
-
def handle_subprocesses_results
|
301
|
-
tests_passed = false
|
302
|
-
FastlaneCore::UI.header("Output from parallelized batch run")
|
303
|
-
@pipe_endpoints.each_with_index do |endpoints, index|
|
304
|
-
mainprocess_reader, = endpoints
|
305
|
-
subprocess_result = parse_subprocess_results(index, mainprocess_reader.read)
|
306
|
-
mainprocess_reader.close
|
307
|
-
stream_subprocess_result_to_console(index, subprocess_result['subprocess_logfilepath'])
|
308
|
-
tests_passed = subprocess_result['tests_passed']
|
309
|
-
end
|
310
|
-
puts '=' * 80
|
311
|
-
tests_passed
|
312
|
-
true
|
313
|
-
end
|
314
|
-
end
|
315
|
-
end
|
316
|
-
end
|
317
|
-
end
|