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,59 @@
|
|
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[:parallelize]
|
11
|
+
setup_scan_config
|
12
|
+
delete_multi_scan_cloned_simulators
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_scan_config
|
17
|
+
unless ::Scan.config&._values.has_key?(:destination)
|
18
|
+
scan_option_keys = Scan.config.all_keys
|
19
|
+
::Scan.config = FastlaneCore::Configuration.create(
|
20
|
+
Fastlane::Actions::ScanAction.available_options,
|
21
|
+
@options.select{ |k| scan_option_keys.include?(k) }
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def clone_destination_simulators
|
27
|
+
cloned_simulators = []
|
28
|
+
|
29
|
+
batch_count = @options[:batch_count] || 0
|
30
|
+
destination_simulator_ids = Scan.config[:destination].map do |destination|
|
31
|
+
destination.split(',id=').last
|
32
|
+
end
|
33
|
+
original_simulators = FastlaneCore::DeviceManager.simulators('iOS').find_all do |simulator|
|
34
|
+
destination_simulator_ids.include?(simulator.udid)
|
35
|
+
end
|
36
|
+
(0...batch_count).each do |batch_index|
|
37
|
+
cloned_simulators << []
|
38
|
+
original_simulators.each do |simulator|
|
39
|
+
FastlaneCore::UI.verbose("Cloning simulator")
|
40
|
+
cloned_simulator = simulator.clone
|
41
|
+
new_first_name = simulator.name.sub(/( ?\(.*| ?$)/, " Clone #{batch_index}")
|
42
|
+
new_last_name = "#{self.class.name}<#{self.object_id}>"
|
43
|
+
cloned_simulator.rename("#{new_first_name} #{new_last_name}")
|
44
|
+
|
45
|
+
cloned_simulators.last << cloned_simulator
|
46
|
+
end
|
47
|
+
end
|
48
|
+
cloned_simulators
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete_multi_scan_cloned_simulators
|
52
|
+
FastlaneCore::DeviceManager.simulators('iOS').each do |simulator|
|
53
|
+
simulator.delete if /#{self.class.name}<\d+>/ =~ simulator.name
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,317 @@
|
|
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
|
@@ -64,9 +64,12 @@ module TestCenter
|
|
64
64
|
numbered_filename(@output_files.to_s.split(',')[junit_index])
|
65
65
|
end
|
66
66
|
|
67
|
-
def junit_reportname
|
67
|
+
def junit_reportname(suffix = '')
|
68
68
|
junit_index = @output_types.split(',').find_index('junit')
|
69
|
-
@output_files.to_s.split(',')[junit_index]
|
69
|
+
report_name = @output_files.to_s.split(',')[junit_index]
|
70
|
+
return report_name if suffix.empty?
|
71
|
+
|
72
|
+
"#{File.basename(report_name, '.*')}-#{suffix}#{junit_filextension}"
|
70
73
|
end
|
71
74
|
|
72
75
|
def junit_filextension
|
@@ -90,9 +93,12 @@ module TestCenter
|
|
90
93
|
numbered_filename(@output_files.to_s.split(',')[html_index])
|
91
94
|
end
|
92
95
|
|
93
|
-
def html_reportname
|
96
|
+
def html_reportname(suffix = '')
|
94
97
|
html_index = @output_types.split(',').find_index('html')
|
95
|
-
@output_files.to_s.split(',')[html_index]
|
98
|
+
report_name = @output_files.to_s.split(',')[html_index]
|
99
|
+
return report_name if suffix.empty?
|
100
|
+
|
101
|
+
"#{File.basename(report_name, '.*')}-#{suffix}#{html_filextension}"
|
96
102
|
end
|
97
103
|
|
98
104
|
def html_filextension
|
@@ -116,9 +122,12 @@ module TestCenter
|
|
116
122
|
numbered_filename(@output_files.to_s.split(',')[json_index])
|
117
123
|
end
|
118
124
|
|
119
|
-
def json_reportname
|
125
|
+
def json_reportname(suffix = '')
|
120
126
|
json_index = @output_types.split(',').find_index('json')
|
121
|
-
@output_files.to_s.split(',')[json_index]
|
127
|
+
report_name = @output_files.to_s.split(',')[json_index]
|
128
|
+
return report_name if suffix.empty?
|
129
|
+
|
130
|
+
"#{File.basename(report_name, '.*')}-#{suffix}#{json_filextension}"
|
122
131
|
end
|
123
132
|
|
124
133
|
def json_filextension
|
@@ -5,6 +5,8 @@ module TestCenter
|
|
5
5
|
require 'plist'
|
6
6
|
|
7
7
|
class TestCollector
|
8
|
+
attr_reader :xctestrun_path
|
9
|
+
|
8
10
|
def initialize(options)
|
9
11
|
unless options[:xctestrun] || options[:derived_data_path]
|
10
12
|
options[:derived_data_path] = default_derived_data_path(options)
|
@@ -15,6 +17,7 @@ module TestCenter
|
|
15
17
|
end
|
16
18
|
@only_testing = options[:only_testing]
|
17
19
|
@skip_testing = options[:skip_testing]
|
20
|
+
@batch_count = options[:batch_count]
|
18
21
|
end
|
19
22
|
|
20
23
|
def default_derived_data_path(options)
|
@@ -34,7 +37,9 @@ module TestCenter
|
|
34
37
|
if @only_testing
|
35
38
|
@testables ||= only_testing_to_testables_tests.keys
|
36
39
|
else
|
37
|
-
@testables ||= Plist.parse_xml(@xctestrun_path).keys
|
40
|
+
@testables ||= Plist.parse_xml(@xctestrun_path).keys.reject do |key|
|
41
|
+
key == '__xctestrun_metadata__'
|
42
|
+
end
|
38
43
|
end
|
39
44
|
end
|
40
45
|
@testables
|
@@ -49,13 +54,30 @@ module TestCenter
|
|
49
54
|
tests
|
50
55
|
end
|
51
56
|
|
57
|
+
def xctestrun_known_tests
|
58
|
+
config = FastlaneCore::Configuration.create(::Fastlane::Actions::TestsFromXctestrunAction.available_options, xctestrun: @xctestrun_path)
|
59
|
+
::Fastlane::Actions::TestsFromXctestrunAction.run(config)
|
60
|
+
end
|
61
|
+
|
52
62
|
def testables_tests
|
53
63
|
unless @testables_tests
|
54
64
|
if @only_testing
|
65
|
+
known_tests = nil
|
55
66
|
@testables_tests = only_testing_to_testables_tests
|
67
|
+
|
68
|
+
@testables_tests.each do |testable, tests|
|
69
|
+
tests.each_with_index do |test, index|
|
70
|
+
if test.count('/') < 2
|
71
|
+
known_tests ||= xctestrun_known_tests[testable]
|
72
|
+
test_components = test.split('/')
|
73
|
+
testsuite = test_components.size == 1 ? test_components[0] : test_components[1]
|
74
|
+
@testables_tests[testable][index] = known_tests.select { |known_test| known_test.include?(testsuite) }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
@testables_tests[testable].flatten!
|
78
|
+
end
|
56
79
|
else
|
57
|
-
|
58
|
-
@testables_tests = ::Fastlane::Actions::TestsFromXctestrunAction.run(config)
|
80
|
+
@testables_tests = xctestrun_known_tests
|
59
81
|
if @skip_testing
|
60
82
|
skipped_testable_tests = Hash.new { |h, k| h[k] = [] }
|
61
83
|
@skip_testing.sort.each do |skipped_test_identifier|
|
@@ -68,8 +90,30 @@ module TestCenter
|
|
68
90
|
end
|
69
91
|
end
|
70
92
|
end
|
93
|
+
|
71
94
|
@testables_tests
|
72
95
|
end
|
96
|
+
|
97
|
+
def test_batches
|
98
|
+
if @batches.nil?
|
99
|
+
@batches = []
|
100
|
+
testables.each do |testable|
|
101
|
+
testable_tests = testables_tests[testable]
|
102
|
+
next if testable_tests.empty?
|
103
|
+
|
104
|
+
if @batch_count > 1
|
105
|
+
slice_count = [(testable_tests.length / @batch_count.to_f).ceil, 1].max
|
106
|
+
testable_tests.each_slice(slice_count).to_a.each do |tests_batch|
|
107
|
+
@batches << tests_batch
|
108
|
+
end
|
109
|
+
else
|
110
|
+
@batches << testable_tests
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
@batches
|
116
|
+
end
|
73
117
|
end
|
74
118
|
end
|
75
119
|
end
|