fastlane-plugin-test_center 2.5.1 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/fastlane/plugin/test_center/actions/multi_scan.rb +98 -198
- data/lib/fastlane/plugin/test_center/actions/tests_from_xctestrun.rb +3 -2
- data/lib/fastlane/plugin/test_center/helper/correcting_scan_helper.rb +163 -0
- data/lib/fastlane/plugin/test_center/helper/reportname_helper.rb +70 -0
- data/lib/fastlane/plugin/test_center/helper/test_collector.rb +60 -0
- data/lib/fastlane/plugin/test_center/version.rb +1 -1
- metadata +23 -6
- data/lib/fastlane/plugin/test_center/helper/test_center_helper.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e52d3f37f8075a9050200d5d5cf19b8fa0b02fb8
|
4
|
+
data.tar.gz: b2c7b4dd8c192c105dbb71a8aa390425c856aa24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4dc6d5e02ea3c35c4504a493b4c0e3197576acaf203b5f92fffb3487a28f5e3309943df72b852df10d012374066f63949a570b319b768046eec2c67113cc342c
|
7
|
+
data.tar.gz: 9b6c0b59e057fe140964369185e2c9373939441f031d5ce19570d521fd961b64ef4d8539f30ccab2a8772fcc7084c13e1436cd2bfeceec739d23f9ecd2660565
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Fastlane
|
2
2
|
module Actions
|
3
|
+
require 'fastlane_core/ui/errors/fastlane_common_error'
|
3
4
|
require 'fastlane/actions/scan'
|
4
5
|
require 'shellwords'
|
5
6
|
require 'xctest_list'
|
@@ -7,218 +8,70 @@ module Fastlane
|
|
7
8
|
|
8
9
|
class MultiScanAction < Action
|
9
10
|
def self.run(params)
|
10
|
-
scan_options = params.values.reject { |k| %i[try_count batch_count].include?(k) }
|
11
|
-
|
12
11
|
unless Helper.test?
|
13
12
|
FastlaneCore::PrintTable.print_values(
|
14
|
-
config: params._values.
|
13
|
+
config: params._values.select { |k, _| %i[try_count batch_count fail_build].include?(k) },
|
15
14
|
title: "Summary for multi_scan (test_center v#{Fastlane::TestCenter::VERSION})"
|
16
15
|
)
|
17
16
|
end
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
unless scan_options[:test_without_building]
|
22
|
-
build_for_testing(scan_options)
|
23
|
-
scan_options.delete(:build_for_testing)
|
24
|
-
scan_options[:test_without_building] = true
|
25
|
-
end
|
26
|
-
|
27
|
-
batch_count = params[:batch_count]
|
28
|
-
if batch_count == 1
|
29
|
-
try_scan_with_retry(scan_options, params[:try_count])
|
30
|
-
else
|
31
|
-
tests = tests_to_batch(scan_options)
|
32
|
-
tests.each_slice((tests.length / batch_count.to_f).round).to_a.each do |tests_batch|
|
33
|
-
scan_options.reject! { |key| key == :skip_testing }
|
34
|
-
scan_options[:only_testing] = tests_batch
|
35
|
-
try_scan_with_retry(scan_options, params[:try_count])
|
36
|
-
end
|
37
|
-
end
|
38
|
-
collate_reports(scan_options)
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.try_scan_with_retry(scan_options, maximum_try_count)
|
42
|
-
try_count = 0
|
43
|
-
begin
|
44
|
-
try_count += 1
|
45
|
-
config = FastlaneCore::Configuration.create(Fastlane::Actions::ScanAction.available_options, scan_options)
|
46
|
-
Fastlane::Actions::ScanAction.run(config)
|
47
|
-
rescue FastlaneCore::Interface::FastlaneTestFailure => e
|
48
|
-
UI.verbose("Scan failed with #{e}")
|
49
|
-
if try_count < maximum_try_count
|
50
|
-
report_filepath = junit_report_filepath(scan_options)
|
51
|
-
failed_tests = other_action.tests_from_junit(junit: report_filepath)[:failed]
|
52
|
-
scan_options[:only_testing] = failed_tests.map(&:shellescape)
|
53
|
-
increment_report_filenames(scan_options)
|
54
|
-
retry
|
55
|
-
end
|
56
|
-
else
|
57
|
-
increment_report_filenames(scan_options)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def self.collate_reports(scan_options)
|
62
|
-
extension = junit_report_fileextension(scan_options)
|
63
|
-
report_files = Dir.glob("#{scan_options[:output_directory]}/*#{extension}").map do |relative_filepath|
|
64
|
-
File.absolute_path(relative_filepath)
|
65
|
-
end
|
66
|
-
if report_files.size > 1
|
67
|
-
r = Regexp.new("^(?<filename>.*)-(\\d+)\\.(junit|xml|html)")
|
68
|
-
final_report_name = junit_report_filename(scan_options)
|
69
|
-
match = r.match(final_report_name)
|
70
|
-
if match
|
71
|
-
final_report_name = "#{match[:filename]}#{extension}"
|
72
|
-
end
|
73
|
-
other_action.collate_junit_reports(
|
74
|
-
reports: report_files.sort { |f1, f2| File.mtime(f1) <=> File.mtime(f2) },
|
75
|
-
collated_report: File.absolute_path(File.join(scan_options[:output_directory], final_report_name))
|
17
|
+
unless params[:test_without_building]
|
18
|
+
build_for_testing(
|
19
|
+
params._values
|
76
20
|
)
|
77
21
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
Actions.sh("killall -9 '#{program}' &> /dev/null || true", log: false)
|
83
|
-
end
|
84
|
-
|
85
|
-
def self.quit_simulators
|
86
|
-
kill('iPhone Simulator')
|
87
|
-
kill('Simulator')
|
88
|
-
kill('SimulatorBridge')
|
89
|
-
|
90
|
-
launchctl_list_count = 0
|
91
|
-
while Actions.sh('launchctl list | grep com.apple.CoreSimulator.CoreSimulatorService || true', log: false) != ''
|
92
|
-
break if (launchctl_list_count += 1) > 10
|
93
|
-
Actions.sh('launchctl remove com.apple.CoreSimulator.CoreSimulatorService &> /dev/null || true', log: false)
|
94
|
-
sleep(1)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def self.tests_to_batch(scan_options)
|
99
|
-
unless scan_options[:only_testing].nil?
|
100
|
-
return scan_options[:only_testing]
|
22
|
+
smart_scanner = ::TestCenter::Helper::CorrectingScanHelper.new(params.values)
|
23
|
+
tests_passed = smart_scanner.scan
|
24
|
+
if params[:fail_build] && !tests_passed
|
25
|
+
raise UI.test_failure!('Tests have failed')
|
101
26
|
end
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
27
|
+
summary = run_summary(params, tests_passed, smart_scanner.retry_total_count)
|
28
|
+
unless Helper.test?
|
29
|
+
FastlaneCore::PrintTable.print_values(
|
30
|
+
config: summary,
|
31
|
+
title: "multi_scan results"
|
32
|
+
)
|
106
33
|
end
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
34
|
+
summary
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.run_summary(scan_options, tests_passed, retry_total_count)
|
38
|
+
reportnamer = ::TestCenter::Helper::ReportNameHelper.new(
|
39
|
+
scan_options[:output_types],
|
40
|
+
scan_options[:output_files],
|
41
|
+
scan_options[:custom_report_file_name]
|
42
|
+
)
|
43
|
+
passing_testcount = 0
|
44
|
+
failed_tests = []
|
45
|
+
report_files = Dir.glob("#{scan_options[:output_directory]}/**/*#{reportnamer.junit_filextension}").map do |relative_filepath|
|
46
|
+
File.absolute_path(relative_filepath)
|
114
47
|
end
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
testsuite.count('/') == 1 # just the testable/testsuite
|
120
|
-
end
|
121
|
-
tests_to_skip -= testsuites_to_skip
|
122
|
-
tests_to_batch.reject! do |test_identifier|
|
123
|
-
test_suite = test_identifier.split('/').first(2).join('/')
|
124
|
-
testsuites_to_skip.include?(test_suite)
|
125
|
-
end
|
126
|
-
tests_to_batch -= tests_to_skip
|
48
|
+
report_files.each do |report_file|
|
49
|
+
junit_results = other_action.tests_from_junit(junit: report_file)
|
50
|
+
failed_tests.concat(junit_results[:failed])
|
51
|
+
passing_testcount += junit_results[:passing].size
|
127
52
|
end
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
xctest_host_path = xctestrun_config['TestHostPath'].sub('__TESTROOT__', xctestrun_rootpath)
|
138
|
-
xctestrun_config['TestBundlePath'].sub!('__TESTHOST__', xctest_host_path)
|
139
|
-
xctestrun_config['TestBundlePath'].sub('__TESTROOT__', xctestrun_rootpath)
|
140
|
-
end
|
141
|
-
|
142
|
-
def self.testrun_path(scan_options)
|
143
|
-
Dir.glob("#{scan_options[:derived_data_path]}/Build/Products/*.xctestrun").first
|
144
|
-
end
|
145
|
-
|
146
|
-
def self.test_products_path(derived_data_path)
|
147
|
-
File.join(derived_data_path, "Build", "Products")
|
53
|
+
{
|
54
|
+
result: tests_passed,
|
55
|
+
total_tests: passing_testcount + failed_tests.size,
|
56
|
+
passing_testcount: passing_testcount,
|
57
|
+
failed_testcount: failed_tests.size,
|
58
|
+
failed_tests: failed_tests,
|
59
|
+
total_retry_count: retry_total_count,
|
60
|
+
report_files: report_files
|
61
|
+
}
|
148
62
|
end
|
149
63
|
|
150
64
|
def self.build_for_testing(scan_options)
|
151
|
-
|
152
|
-
|
153
|
-
|
65
|
+
config = FastlaneCore::Configuration.create(
|
66
|
+
Fastlane::Actions::ScanAction.available_options,
|
67
|
+
scan_options.merge(build_for_testing: true).reject { |k, _| %i[try_count batch_count test_without_building].include?(k) }
|
68
|
+
)
|
154
69
|
Fastlane::Actions::ScanAction.run(config)
|
155
|
-
scan_options[:derived_data_path] = Scan.config[:derived_data_path]
|
156
|
-
end
|
157
|
-
|
158
|
-
def self.config_has_junit_report(config)
|
159
|
-
output_types = config.fetch(:output_types, '').to_s.split(',')
|
160
|
-
output_filenames = config.fetch(:output_files, '').to_s.split(',')
|
161
|
-
|
162
|
-
output_type_file_count_match = output_types.size == output_filenames.size
|
163
|
-
output_types.include?('junit') && (output_type_file_count_match || config[:custom_report_file_name].to_s.strip.length > 0)
|
164
|
-
end
|
165
|
-
|
166
|
-
def self.config_with_junit_report(config)
|
167
|
-
return config if config_has_junit_report(config)
|
168
|
-
|
169
|
-
if config[:output_types].to_s.strip.empty? || config[:custom_report_file_name]
|
170
|
-
config[:custom_report_file_name] ||= 'report.xml'
|
171
|
-
config[:output_types] = 'junit'
|
172
|
-
elsif config[:output_types].strip == 'junit' && config[:output_files].to_s.strip.empty?
|
173
|
-
config[:custom_report_file_name] ||= 'report.xml'
|
174
|
-
elsif !config[:output_types].split(',').include?('junit')
|
175
|
-
config[:output_types] << ',junit'
|
176
|
-
config[:output_files] << ',report.xml'
|
177
|
-
elsif config[:output_files].nil?
|
178
|
-
config[:output_files] = config[:output_types].split(',').map { |type| "report.#{type}" }.join(',')
|
179
|
-
end
|
180
|
-
config
|
181
|
-
end
|
182
|
-
|
183
|
-
def self.junit_report_filename(config)
|
184
|
-
report_filename = config[:custom_report_file_name]
|
185
|
-
if report_filename.nil?
|
186
|
-
junit_index = config[:output_types].split(',').find_index('junit')
|
187
|
-
report_filename = config[:output_files].to_s.split(',')[junit_index]
|
188
|
-
end
|
189
|
-
report_filename
|
190
|
-
end
|
191
70
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
def self.junit_report_filepath(config)
|
197
|
-
File.absolute_path(File.join(config[:output_directory], junit_report_filename(config)))
|
198
|
-
end
|
199
|
-
|
200
|
-
def self.increment_report_filename(filename)
|
201
|
-
new_report_number = 2
|
202
|
-
basename = File.basename(filename, '.*')
|
203
|
-
r = Regexp.new("^(?<filename>.*)-(?<report_number>\\d+)\\.(junit|xml|html)")
|
204
|
-
match = r.match(filename)
|
205
|
-
if match
|
206
|
-
new_report_number = match[:report_number].to_i + 1
|
207
|
-
basename = match[:filename]
|
208
|
-
end
|
209
|
-
"#{basename}-#{new_report_number}#{File.extname(filename)}"
|
210
|
-
end
|
211
|
-
|
212
|
-
def self.increment_report_filenames(config)
|
213
|
-
if config[:output_files]
|
214
|
-
output_files = config[:output_files].to_s.split(',')
|
215
|
-
output_files = output_files.map do |output_file|
|
216
|
-
increment_report_filename(output_file)
|
217
|
-
end
|
218
|
-
config[:output_files] = output_files.join(',')
|
219
|
-
elsif config[:custom_report_file_name]
|
220
|
-
config[:custom_report_file_name] = increment_report_filename(config[:custom_report_file_name])
|
221
|
-
end
|
71
|
+
scan_options.merge!(
|
72
|
+
test_without_building: true,
|
73
|
+
derived_data_path: Scan.config[:derived_data_path]
|
74
|
+
).delete(:build_for_testing)
|
222
75
|
end
|
223
76
|
|
224
77
|
#####################################################
|
@@ -226,11 +79,14 @@ module Fastlane
|
|
226
79
|
#####################################################
|
227
80
|
|
228
81
|
def self.description
|
229
|
-
"Uses scan to run Xcode tests a given number of times
|
82
|
+
"Uses scan to run Xcode tests a given number of times, with the option " \
|
83
|
+
"of batching them, only re-testing failing tests."
|
230
84
|
end
|
231
85
|
|
232
86
|
def self.details
|
233
|
-
"Use this action to run your tests if you have fragile tests that fail
|
87
|
+
"Use this action to run your tests if you have fragile tests that fail" \
|
88
|
+
"sporadically, if you have a huge number of tests that should be " \
|
89
|
+
"batched, or have multiple test targets and need meaningful junit reports."
|
234
90
|
end
|
235
91
|
|
236
92
|
def self.scan_options
|
@@ -258,10 +114,54 @@ module Fastlane
|
|
258
114
|
verify_block: proc do |count|
|
259
115
|
UI.user_error!("Error: Batch counts must be greater than zero") unless count > 0
|
260
116
|
end
|
117
|
+
),
|
118
|
+
FastlaneCore::ConfigItem.new(
|
119
|
+
key: :testrun_completed_block,
|
120
|
+
description: 'A block invoked each time a test run completes',
|
121
|
+
optional: true,
|
122
|
+
is_string: false,
|
123
|
+
default_value: nil
|
261
124
|
)
|
262
125
|
]
|
263
126
|
end
|
264
127
|
|
128
|
+
def self.example_code
|
129
|
+
[
|
130
|
+
'multi_scan(
|
131
|
+
project: File.absolute_path("../AtomicBoy/AtomicBoy.xcodeproj"),
|
132
|
+
scheme: "Professor",
|
133
|
+
try_count: 3,
|
134
|
+
custom_report_file_name: "atomic_report.xml",
|
135
|
+
output_types: "junit"
|
136
|
+
)',
|
137
|
+
'multi_scan(
|
138
|
+
project: File.absolute_path("../GalaxyFamily/GalaxyFamily.xcodeproj"),
|
139
|
+
scheme: "Everything",
|
140
|
+
try_count: 3,
|
141
|
+
batch_count: 2, # splits the tests into two chunks to not overload the iOS Simulator
|
142
|
+
custom_report_file_name: "atomic_report.xml",
|
143
|
+
output_types: "junit"
|
144
|
+
)',
|
145
|
+
'multi_scan(
|
146
|
+
project: File.absolute_path("../GalaxyFamily/GalaxyFamily.xcodeproj"),
|
147
|
+
scheme: "Everything",
|
148
|
+
try_count: 3,
|
149
|
+
batch_count: 8, # splits the tests into 8 chunks to not overload the iOS Simulator
|
150
|
+
testrun_completed_block: lambda { |testrun_info|
|
151
|
+
passed_test_count = testrun_info[:failed].size
|
152
|
+
failed_test_count = testrun_info[:passing].size
|
153
|
+
try_attempt = testrun_info[:try_count]
|
154
|
+
batch = testrun_info[:batch]
|
155
|
+
|
156
|
+
if failed_test_count > passed_test_count / 2
|
157
|
+
UI.abort_with_message!("Too many tests are failing")
|
158
|
+
end
|
159
|
+
UI.message("😊 everything is fine, let\'s continue try #{try_attempt} for batch #{batch}")
|
160
|
+
}
|
161
|
+
)'
|
162
|
+
]
|
163
|
+
end
|
164
|
+
|
265
165
|
def self.authors
|
266
166
|
["lyndsey-ferguson/@lyndseydf"]
|
267
167
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'plist'
|
2
|
+
|
1
3
|
module Fastlane
|
2
4
|
module Actions
|
3
5
|
class TestsFromXctestrunAction < Action
|
@@ -23,8 +25,7 @@ module Fastlane
|
|
23
25
|
|
24
26
|
def self.xctest_bundle_path(xctestrun_rootpath, xctestrun_config)
|
25
27
|
xctest_host_path = xctestrun_config['TestHostPath'].sub('__TESTROOT__', xctestrun_rootpath)
|
26
|
-
xctestrun_config['TestBundlePath'].sub
|
27
|
-
xctestrun_config['TestBundlePath'].sub('__TESTROOT__', xctestrun_rootpath)
|
28
|
+
xctestrun_config['TestBundlePath'].sub('__TESTHOST__', xctest_host_path).sub('__TESTROOT__', xctestrun_rootpath)
|
28
29
|
end
|
29
30
|
|
30
31
|
#####################################################
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module TestCenter
|
2
|
+
module Helper
|
3
|
+
require 'fastlane_core/ui/ui.rb'
|
4
|
+
require 'plist'
|
5
|
+
|
6
|
+
class CorrectingScanHelper
|
7
|
+
attr_reader :retry_total_count
|
8
|
+
|
9
|
+
def initialize(multi_scan_options)
|
10
|
+
@batch_count = multi_scan_options[:batch_count] || 1
|
11
|
+
@output_directory = multi_scan_options[:output_directory] || 'test_results'
|
12
|
+
@try_count = multi_scan_options[:try_count]
|
13
|
+
@retry_total_count = 0
|
14
|
+
@testrun_completed_block = multi_scan_options[:testrun_completed_block]
|
15
|
+
@given_custom_report_file_name = multi_scan_options[:custom_report_file_name]
|
16
|
+
@given_output_types = multi_scan_options[:output_types]
|
17
|
+
@given_output_files = multi_scan_options[:output_files]
|
18
|
+
@scan_options = multi_scan_options.reject do |option, _|
|
19
|
+
%i[
|
20
|
+
output_directory
|
21
|
+
only_testing
|
22
|
+
skip_testing
|
23
|
+
try_count
|
24
|
+
batch_count
|
25
|
+
custom_report_file_name
|
26
|
+
fail_build
|
27
|
+
testrun_completed_block
|
28
|
+
].include?(option)
|
29
|
+
end
|
30
|
+
@test_collector = TestCollector.new(multi_scan_options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def scan
|
34
|
+
tests_passed = true
|
35
|
+
@testables_count = @test_collector.testables.size
|
36
|
+
@test_collector.testables.each do |testable|
|
37
|
+
tests_passed = scan_testable(testable) && tests_passed
|
38
|
+
end
|
39
|
+
tests_passed
|
40
|
+
end
|
41
|
+
|
42
|
+
def scan_testable(testable)
|
43
|
+
tests_passed = true
|
44
|
+
reportnamer = ReportNameHelper.new(
|
45
|
+
@given_output_types,
|
46
|
+
@given_output_files,
|
47
|
+
@given_custom_report_file_name
|
48
|
+
)
|
49
|
+
output_directory = @output_directory
|
50
|
+
if @batch_count > 1 || @testables_count > 1
|
51
|
+
testable_tests = @test_collector.testables_tests[testable]
|
52
|
+
current_batch = 1
|
53
|
+
testable_tests.each_slice((testable_tests.length / @batch_count.to_f).round).to_a.each do |tests_batch|
|
54
|
+
if @testables_count > 1
|
55
|
+
output_directory = File.join(@output_directory, "results-#{testable}")
|
56
|
+
end
|
57
|
+
FastlaneCore::UI.header("Starting test run on testable '#{testable}'")
|
58
|
+
tests_passed = correcting_scan(
|
59
|
+
{
|
60
|
+
only_testing: tests_batch,
|
61
|
+
output_directory: output_directory
|
62
|
+
},
|
63
|
+
current_batch,
|
64
|
+
reportnamer
|
65
|
+
) && tests_passed
|
66
|
+
current_batch += 1
|
67
|
+
reportnamer.increment
|
68
|
+
end
|
69
|
+
else
|
70
|
+
options = {
|
71
|
+
output_directory: output_directory
|
72
|
+
}
|
73
|
+
options[:skip_testing] = @skip_testing if @skip_testing
|
74
|
+
tests_passed = correcting_scan(options, 1, reportnamer) && tests_passed
|
75
|
+
end
|
76
|
+
collate_reports(output_directory, reportnamer)
|
77
|
+
tests_passed
|
78
|
+
end
|
79
|
+
|
80
|
+
def collate_reports(output_directory, reportnamer)
|
81
|
+
report_files = Dir.glob("#{output_directory}/*#{reportnamer.junit_filextension}").map do |relative_filepath|
|
82
|
+
File.absolute_path(relative_filepath)
|
83
|
+
end
|
84
|
+
if report_files.size > 1
|
85
|
+
config = FastlaneCore::Configuration.create(
|
86
|
+
Fastlane::Actions::CollateJunitReportsAction.available_options,
|
87
|
+
{
|
88
|
+
reports: report_files.sort { |f1, f2| File.mtime(f1) <=> File.mtime(f2) },
|
89
|
+
collated_report: File.absolute_path(File.join(output_directory, reportnamer.junit_reportname))
|
90
|
+
}
|
91
|
+
)
|
92
|
+
Fastlane::Actions::CollateJunitReportsAction.run(config)
|
93
|
+
end
|
94
|
+
retried_junit_reportfiles = Dir.glob("#{output_directory}/**/*-[1-9]*#{reportnamer.junit_filextension}")
|
95
|
+
FileUtils.rm_f(retried_junit_reportfiles)
|
96
|
+
end
|
97
|
+
|
98
|
+
def correcting_scan(scan_run_options, batch, reportnamer)
|
99
|
+
scan_options = @scan_options.merge(scan_run_options)
|
100
|
+
try_count = 0
|
101
|
+
tests_passed = true
|
102
|
+
begin
|
103
|
+
try_count += 1
|
104
|
+
config = FastlaneCore::Configuration.create(
|
105
|
+
Fastlane::Actions::ScanAction.available_options,
|
106
|
+
scan_options.merge(reportnamer.scan_options)
|
107
|
+
)
|
108
|
+
quit_simulators
|
109
|
+
Fastlane::Actions::ScanAction.run(config)
|
110
|
+
@testrun_completed_block && @testrun_completed_block.call(
|
111
|
+
testrun_info(batch, try_count, reportnamer, scan_options[:output_directory])
|
112
|
+
)
|
113
|
+
tests_passed = true
|
114
|
+
rescue FastlaneCore::Interface::FastlaneTestFailure => e
|
115
|
+
FastlaneCore::UI.verbose("Scan failed with #{e}")
|
116
|
+
if try_count < @try_count
|
117
|
+
@retry_total_count += 1
|
118
|
+
|
119
|
+
info = testrun_info(batch, try_count, reportnamer, scan_options[:output_directory])
|
120
|
+
@testrun_completed_block && @testrun_completed_block.call(
|
121
|
+
info
|
122
|
+
)
|
123
|
+
scan_options[:only_testing] = info[:failed].map(&:shellescape)
|
124
|
+
FastlaneCore::UI.message('Re-running scan on only failed tests')
|
125
|
+
reportnamer.increment
|
126
|
+
retry
|
127
|
+
end
|
128
|
+
tests_passed = false
|
129
|
+
end
|
130
|
+
tests_passed
|
131
|
+
end
|
132
|
+
|
133
|
+
def testrun_info(batch, try_count, reportnamer, output_directory)
|
134
|
+
report_filepath = File.join(output_directory, reportnamer.junit_last_reportname)
|
135
|
+
config = FastlaneCore::Configuration.create(
|
136
|
+
Fastlane::Actions::TestsFromJunitAction.available_options,
|
137
|
+
{
|
138
|
+
junit: File.absolute_path(report_filepath)
|
139
|
+
}
|
140
|
+
)
|
141
|
+
junit_results = Fastlane::Actions::TestsFromJunitAction.run(config)
|
142
|
+
|
143
|
+
{
|
144
|
+
failed: junit_results[:failed],
|
145
|
+
passing: junit_results[:passing],
|
146
|
+
batch: batch,
|
147
|
+
try_count: try_count,
|
148
|
+
report_filepath: report_filepath
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
def quit_simulators
|
153
|
+
Fastlane::Actions.sh("killall -9 'iPhone Simulator' 'Simulator' 'SimulatorBridge' &> /dev/null || true", log: false)
|
154
|
+
launchctl_list_count = 0
|
155
|
+
while Fastlane::Actions.sh('launchctl list | grep com.apple.CoreSimulator.CoreSimulatorService || true', log: false) != ''
|
156
|
+
break if (launchctl_list_count += 1) > 10
|
157
|
+
Fastlane::Actions.sh('launchctl remove com.apple.CoreSimulator.CoreSimulatorService &> /dev/null || true', log: false)
|
158
|
+
sleep(1)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module TestCenter
|
2
|
+
module Helper
|
3
|
+
require 'fastlane_core/ui/ui.rb'
|
4
|
+
|
5
|
+
class ReportNameHelper
|
6
|
+
def initialize(output_types = nil, output_files = nil, custom_report_file_name = nil)
|
7
|
+
@output_types = output_types || 'junit'
|
8
|
+
@output_files = output_files || custom_report_file_name
|
9
|
+
@report_count = 0
|
10
|
+
|
11
|
+
if @output_types && @output_files.nil?
|
12
|
+
@output_files = @output_types.split(',').map { |type| "report.#{type}" }.join(',')
|
13
|
+
end
|
14
|
+
unless @output_types.include?('junit')
|
15
|
+
FastlaneCore::UI.important('Scan output types missing \'junit\', adding it')
|
16
|
+
@output_types = @output_types.split(',').push('junit').join(',')
|
17
|
+
if @output_types.split(',').size == @output_files.split(',').size + 1
|
18
|
+
@output_files = @output_files.split(',').push('report.xml').join(',')
|
19
|
+
FastlaneCore::UI.message('As output files has one less than the new number of output types, assumming the filename for the junit was missing and added it')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
types = @output_types.split(',').each(&:chomp)
|
24
|
+
files = @output_files.split(',').each(&:chomp)
|
25
|
+
unless files.size == types.size
|
26
|
+
raise ArgumentError, "Error: count of :output_types, #{types}, does not match the output filename(s) #{files}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def numbered_filename(filename)
|
31
|
+
if @report_count > 0
|
32
|
+
basename = File.basename(filename, '.*')
|
33
|
+
extension = File.extname(filename)
|
34
|
+
filename = "#{basename}-#{@report_count + 1}#{extension}"
|
35
|
+
end
|
36
|
+
filename
|
37
|
+
end
|
38
|
+
|
39
|
+
def scan_options
|
40
|
+
files = @output_files.split(',').each(&:chomp)
|
41
|
+
files.map! do |filename|
|
42
|
+
filename.chomp
|
43
|
+
numbered_filename(filename)
|
44
|
+
end
|
45
|
+
{
|
46
|
+
output_types: @output_types,
|
47
|
+
output_files: files.join(',')
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def junit_last_reportname
|
52
|
+
junit_index = @output_types.split(',').find_index('junit')
|
53
|
+
numbered_filename(@output_files.to_s.split(',')[junit_index])
|
54
|
+
end
|
55
|
+
|
56
|
+
def junit_reportname
|
57
|
+
junit_index = @output_types.split(',').find_index('junit')
|
58
|
+
@output_files.to_s.split(',')[junit_index]
|
59
|
+
end
|
60
|
+
|
61
|
+
def junit_filextension
|
62
|
+
File.extname(junit_reportname)
|
63
|
+
end
|
64
|
+
|
65
|
+
def increment
|
66
|
+
@report_count += 1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module TestCenter
|
2
|
+
module Helper
|
3
|
+
require 'fastlane_core/ui/ui.rb'
|
4
|
+
require 'plist'
|
5
|
+
|
6
|
+
class TestCollector
|
7
|
+
def initialize(options)
|
8
|
+
@xctestrun_path = options[:xctestrun] || derived_testrun_path(options[:derived_data_path], options[:scheme])
|
9
|
+
@only_testing = options[:only_testing]
|
10
|
+
@skip_testing = options[:skip_testing]
|
11
|
+
end
|
12
|
+
|
13
|
+
def derived_testrun_path(derived_data_path, scheme)
|
14
|
+
Dir.glob("#{derived_data_path}/Build/Products/#{scheme}*.xctestrun").first
|
15
|
+
end
|
16
|
+
|
17
|
+
def testables
|
18
|
+
unless @testables
|
19
|
+
if @only_testing
|
20
|
+
@testables ||= only_testing_to_testables_tests.keys
|
21
|
+
else
|
22
|
+
@testables ||= Plist.parse_xml(@xctestrun_path).keys
|
23
|
+
end
|
24
|
+
end
|
25
|
+
@testables
|
26
|
+
end
|
27
|
+
|
28
|
+
def only_testing_to_testables_tests
|
29
|
+
tests = Hash.new { |h, k| h[k] = [] }
|
30
|
+
@only_testing.sort.each do |test_identifier|
|
31
|
+
testable = test_identifier.split('/', 2)[0]
|
32
|
+
tests[testable] << test_identifier
|
33
|
+
end
|
34
|
+
tests
|
35
|
+
end
|
36
|
+
|
37
|
+
def testables_tests
|
38
|
+
unless @testables_tests
|
39
|
+
if @only_testing
|
40
|
+
@testables_tests = only_testing_to_testables_tests
|
41
|
+
else
|
42
|
+
config = FastlaneCore::Configuration.create(::Fastlane::Actions::TestsFromXctestrunAction.available_options, xctestrun: @xctestrun_path)
|
43
|
+
@testables_tests = ::Fastlane::Actions::TestsFromXctestrunAction.run(config)
|
44
|
+
if @skip_testing
|
45
|
+
skipped_testable_tests = Hash.new { |h, k| h[k] = [] }
|
46
|
+
@skip_testing.sort.each do |skipped_test_identifier|
|
47
|
+
testable = skipped_test_identifier.split('/', 2)[0]
|
48
|
+
skipped_testable_tests[testable] << skipped_test_identifier
|
49
|
+
end
|
50
|
+
@testables_tests.each_key do |testable|
|
51
|
+
@testables_tests[testable] -= skipped_testable_tests[testable]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
@testables_tests
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastlane-plugin-test_center
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lyndsey Ferguson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-02-
|
11
|
+
date: 2018-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: plist
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: xcodeproj
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,13 +53,13 @@ dependencies:
|
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: 1.1.2
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
56
|
+
name: bundler
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - ">="
|
46
60
|
- !ruby/object:Gem::Version
|
47
61
|
version: '0'
|
48
|
-
type: :
|
62
|
+
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
@@ -53,7 +67,7 @@ dependencies:
|
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
70
|
+
name: colorize
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - ">="
|
@@ -188,8 +202,10 @@ files:
|
|
188
202
|
- lib/fastlane/plugin/test_center/actions/suppressed_tests.rb
|
189
203
|
- lib/fastlane/plugin/test_center/actions/tests_from_junit.rb
|
190
204
|
- lib/fastlane/plugin/test_center/actions/tests_from_xctestrun.rb
|
205
|
+
- lib/fastlane/plugin/test_center/helper/correcting_scan_helper.rb
|
191
206
|
- lib/fastlane/plugin/test_center/helper/junit_helper.rb
|
192
|
-
- lib/fastlane/plugin/test_center/helper/
|
207
|
+
- lib/fastlane/plugin/test_center/helper/reportname_helper.rb
|
208
|
+
- lib/fastlane/plugin/test_center/helper/test_collector.rb
|
193
209
|
- lib/fastlane/plugin/test_center/helper/xcodebuild_string.rb
|
194
210
|
- lib/fastlane/plugin/test_center/version.rb
|
195
211
|
homepage: https://github.com/lyndsey-ferguson/fastlane-plugin-test_center
|
@@ -217,3 +233,4 @@ signing_key:
|
|
217
233
|
specification_version: 4
|
218
234
|
summary: Makes testing your iOS app easier
|
219
235
|
test_files: []
|
236
|
+
has_rdoc:
|
@@ -1,12 +0,0 @@
|
|
1
|
-
module Fastlane
|
2
|
-
module Helper
|
3
|
-
class TestCenterHelper
|
4
|
-
# class methods that you define here become available in your action
|
5
|
-
# as `Helper::TestCenterHelper.your_method`
|
6
|
-
#
|
7
|
-
def self.show_message
|
8
|
-
UI.message("Hello from the test_center plugin helper!")
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|