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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '09726aaa2b1ba6691e8738682e25aebc1dab29e1bcbb9eef7672b33fac880fec'
|
4
|
+
data.tar.gz: a970fc5ed7a5c8dd05ddd11a2b7865bcc83154469da7e0d336da5f8e6a4e7c20
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d3ad1b1f6854500f9472bdd4e74a5997e7554c04eda123a9d3adf1ce306f522b3f0d7f37dac34895dbf142e4476db5bb34681ad70c6c339e1b976271b518ca2
|
7
|
+
data.tar.gz: f421b8d3548e0c16abcac15ded2f5a8fa8013946f85bb131b68dc5b2ee6d6e4548b029f00ff1e5bacfa5714266feb29db2c2ed8efa3e83e31f563bbf71efd542
|
@@ -4,7 +4,7 @@ module Fastlane
|
|
4
4
|
module TestCenter
|
5
5
|
# Return all .rb files inside the "actions" and "helper" directory
|
6
6
|
def self.all_classes
|
7
|
-
Dir[File.expand_path('**/{actions,helper}
|
7
|
+
Dir[File.expand_path('**/{actions,helper}/**/*.rb', File.dirname(__FILE__))]
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
@@ -14,7 +14,7 @@ module Fastlane
|
|
14
14
|
collate_bundles(base_bundle_path, other_bundlepath)
|
15
15
|
end
|
16
16
|
end
|
17
|
-
FileUtils.rm_rf(params[:collated_bundle])
|
17
|
+
# FileUtils.rm_rf(params[:collated_bundle])
|
18
18
|
FileUtils.cp_r(base_bundle_path, params[:collated_bundle])
|
19
19
|
UI.message("Finished collating test_result bundle to '#{params[:collated_bundle]}'")
|
20
20
|
end
|
@@ -5,6 +5,7 @@ module Fastlane
|
|
5
5
|
require 'shellwords'
|
6
6
|
require 'xctest_list'
|
7
7
|
require 'plist'
|
8
|
+
require_relative '../helper/multi_scan_manager/runner'
|
8
9
|
|
9
10
|
class MultiScanAction < Action
|
10
11
|
def self.run(params)
|
@@ -19,9 +20,13 @@ module Fastlane
|
|
19
20
|
params._values
|
20
21
|
)
|
21
22
|
end
|
22
|
-
|
23
|
-
tests_passed =
|
24
|
-
|
23
|
+
runner = ::TestCenter::Helper::MultiScanManager::Runner.new(params.values)
|
24
|
+
tests_passed = runner.scan
|
25
|
+
if params[:fail_build] && !tests_passed
|
26
|
+
raise UI.test_failure!('Tests have failed')
|
27
|
+
end
|
28
|
+
|
29
|
+
summary = run_summary(params, tests_passed, runner.retry_total_count)
|
25
30
|
unless Helper.test?
|
26
31
|
FastlaneCore::PrintTable.print_values(
|
27
32
|
config: summary,
|
@@ -84,17 +89,19 @@ module Fastlane
|
|
84
89
|
options_to_remove = %i[
|
85
90
|
try_count
|
86
91
|
batch_count
|
92
|
+
output_files
|
93
|
+
parallelize
|
87
94
|
quit_simulators
|
88
95
|
testrun_completed_block
|
89
96
|
test_without_building
|
90
97
|
output_types
|
91
|
-
output_files
|
92
98
|
]
|
93
99
|
config = FastlaneCore::Configuration.create(
|
94
100
|
Fastlane::Actions::ScanAction.available_options,
|
95
101
|
scan_options.merge(build_for_testing: true).reject { |k, _| options_to_remove.include?(k) }
|
96
102
|
)
|
97
103
|
Fastlane::Actions::ScanAction.run(config)
|
104
|
+
remove_build_report_files
|
98
105
|
|
99
106
|
scan_options.merge!(
|
100
107
|
test_without_building: true,
|
@@ -102,6 +109,16 @@ module Fastlane
|
|
102
109
|
).delete(:build_for_testing)
|
103
110
|
end
|
104
111
|
|
112
|
+
def self.remove_build_report_files
|
113
|
+
report_options = Scan::XCPrettyReporterOptionsGenerator.generate_from_scan_config
|
114
|
+
output_files = report_options.instance_variable_get(:@output_files)
|
115
|
+
output_directory = report_options.instance_variable_get(:@output_directory)
|
116
|
+
|
117
|
+
output_files.each do |output_file|
|
118
|
+
FileUtils.rm_f(File.join(output_directory, output_file))
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
105
122
|
#####################################################
|
106
123
|
# @!group Documentation
|
107
124
|
#####################################################
|
@@ -172,6 +189,13 @@ module Fastlane
|
|
172
189
|
description: "Comma separated list of the output types (e.g. html, junit, json, json-compilation-database)",
|
173
190
|
default_value: "html,junit"
|
174
191
|
),
|
192
|
+
FastlaneCore::ConfigItem.new(
|
193
|
+
key: :parallelize,
|
194
|
+
description: 'Run each batch of tests and/or each test target in parallel on its own Simulator',
|
195
|
+
optional: true,
|
196
|
+
is_string: false,
|
197
|
+
default_value: false
|
198
|
+
),
|
175
199
|
FastlaneCore::ConfigItem.new(
|
176
200
|
key: :testrun_completed_block,
|
177
201
|
description: 'A block invoked each time a test run completes',
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Actions
|
3
|
+
class RestartCoreSimulatorServiceAction < Action
|
4
|
+
def self.run(params)
|
5
|
+
launchctl_list_count = 0
|
6
|
+
while Actions.sh('launchctl list | grep com.apple.CoreSimulator.CoreSimulatorService || true', log: false) != ''
|
7
|
+
UI.crash!('Unable to quit com.apple.CoreSimulator.CoreSimulatorService after 10 tries') if (launchctl_list_count += 1) > 10
|
8
|
+
commands << Actions.sh('launchctl stop com.apple.CoreSimulator.CoreSimulatorService &> /dev/null || true', log: false)
|
9
|
+
UI.verbose('Waiting for com.apple.CoreSimulator.CoreSimulatorService to quit')
|
10
|
+
sleep(0.25)
|
11
|
+
end
|
12
|
+
commands << Actions.sh('launchctl start com.apple.CoreSimulator.CoreSimulatorService &> /dev/null || true', log: false)
|
13
|
+
end
|
14
|
+
|
15
|
+
#####################################################
|
16
|
+
# @!group Documentation
|
17
|
+
#####################################################
|
18
|
+
|
19
|
+
def self.description
|
20
|
+
"Restarts the com.apple.CoreSimulator.CoreSimulatorService."
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.details
|
24
|
+
"Sometimes the com.apple.CoreSimulator.CoreSimulatorService can hang. " \
|
25
|
+
"Use this action to force-restart it."
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.authors
|
29
|
+
["lyndsey-ferguson/@lyndseydf"]
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.is_supported?(platform)
|
33
|
+
platform == :ios
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -28,7 +28,7 @@ module Fastlane
|
|
28
28
|
UI.important("Is the Build Setting, `ENABLE_TESTABILITY` enabled for the test target #{testable_name}?")
|
29
29
|
end
|
30
30
|
tests[testable_name] = test_identifiers.map do |test_identifier|
|
31
|
-
"#{testable_name.
|
31
|
+
"#{testable_name.shellsafe_testidentifier}/#{test_identifier}"
|
32
32
|
end
|
33
33
|
end
|
34
34
|
tests
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module FastlaneCore
|
2
|
+
class DeviceManager
|
3
|
+
class Device
|
4
|
+
def clone
|
5
|
+
raise 'Can only clone iOS Simulators' unless self.is_simulator
|
6
|
+
Device.new(
|
7
|
+
name: self.name,
|
8
|
+
udid: `xcrun simctl clone #{self.udid} '#{self.name}'`.chomp,
|
9
|
+
os_type: self.os_type,
|
10
|
+
os_version: self.os_version,
|
11
|
+
state: self.state,
|
12
|
+
is_simulator: self.is_simulator
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def rename(newname)
|
17
|
+
`xcrun simctl rename #{self.udid} '#{newname}'`
|
18
|
+
self.name = newname
|
19
|
+
end
|
20
|
+
|
21
|
+
def boot
|
22
|
+
`xcrun simctl boot #{self.udid}`
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module TestCenter
|
2
|
+
module Helper
|
3
|
+
module MultiScanManager
|
4
|
+
class Interstitial
|
5
|
+
|
6
|
+
attr_writer :output_directory
|
7
|
+
attr_writer :batch
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
@output_directory = options[:output_directory]
|
11
|
+
@testrun_completed_block = options[:testrun_completed_block]
|
12
|
+
@result_bundle = options[:result_bundle]
|
13
|
+
@scheme = options[:scheme]
|
14
|
+
@batch = options[:batch]
|
15
|
+
@reportnamer = options[:reportnamer]
|
16
|
+
@xcpretty_json_file_output = ENV['XCPRETTY_JSON_FILE_OUTPUT']
|
17
|
+
@parallelize = options[:parallelize]
|
18
|
+
|
19
|
+
before_all
|
20
|
+
end
|
21
|
+
|
22
|
+
# TODO: Should we be creating a new interstitial for each batch? yes.
|
23
|
+
# Should we clear out the result bundles before each batch? --> should
|
24
|
+
# it not be done before all batches? Same with env var for json resports.
|
25
|
+
def before_all
|
26
|
+
if @result_bundle
|
27
|
+
remove_preexisting_test_result_bundles
|
28
|
+
end
|
29
|
+
set_json_env_if_necessary
|
30
|
+
if @parallelize
|
31
|
+
@original_derived_data_path = ENV['SCAN_DERIVED_DATA_PATH']
|
32
|
+
FileUtils.mkdir_p(@output_directory)
|
33
|
+
ENV['SCAN_DERIVED_DATA_PATH'] = Dir.mktmpdir(nil, @output_directory)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def after_all
|
38
|
+
FastlaneCore::UI.message("resetting JSON ENV var to #{@xcpretty_json_file_output}")
|
39
|
+
ENV['XCPRETTY_JSON_FILE_OUTPUT'] = @xcpretty_json_file_output
|
40
|
+
if @parallelize
|
41
|
+
ENV['SCAN_DERIVED_DATA_PATH'] = @original_derived_data_path
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def remove_preexisting_test_result_bundles
|
46
|
+
glob_pattern = "#{@output_directory}/.*\.test_result"
|
47
|
+
preexisting_test_result_bundles = Dir.glob(glob_pattern)
|
48
|
+
FileUtils.rm_rf(preexisting_test_result_bundles)
|
49
|
+
end
|
50
|
+
|
51
|
+
def move_test_result_bundle_for_next_run
|
52
|
+
if @result_bundle
|
53
|
+
built_test_result, moved_test_result = test_result_bundlepaths
|
54
|
+
FileUtils.mv(built_test_result, moved_test_result)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_result_bundlepaths
|
59
|
+
[
|
60
|
+
File.join(@output_directory, @scheme) + '.test_result',
|
61
|
+
File.join(@output_directory, @scheme) + "_#{@reportnamer.report_count}.test_result"
|
62
|
+
]
|
63
|
+
end
|
64
|
+
|
65
|
+
def reset_simulators
|
66
|
+
destinations = Scan.config[:destination]
|
67
|
+
simulators = FastlaneCore::DeviceManager.simulators('iOS')
|
68
|
+
simulator_ids_to_reset = []
|
69
|
+
destinations.each do |destination|
|
70
|
+
destination.split(',').each do |destination_pair|
|
71
|
+
key, value = destination_pair.split('=')
|
72
|
+
if key == 'id'
|
73
|
+
simulator_ids_to_reset << value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
simulators_to_reset = simulators.each.select { |simulator| simulator_ids_to_reset.include?(simulator.udid) }
|
78
|
+
simulators_to_reset.each do |simulator|
|
79
|
+
simulator.reset
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def send_info_for_try(try_count)
|
84
|
+
puts "in send_info_for_try for #{@batch}"
|
85
|
+
return unless @testrun_completed_block
|
86
|
+
|
87
|
+
report_filepath = File.join(@output_directory, @reportnamer.junit_last_reportname)
|
88
|
+
|
89
|
+
config = FastlaneCore::Configuration.create(
|
90
|
+
Fastlane::Actions::TestsFromJunitAction.available_options,
|
91
|
+
{
|
92
|
+
junit: File.absolute_path(report_filepath)
|
93
|
+
}
|
94
|
+
)
|
95
|
+
junit_results = Fastlane::Actions::TestsFromJunitAction.run(config)
|
96
|
+
info = {
|
97
|
+
failed: junit_results[:failed],
|
98
|
+
passing: junit_results[:passing],
|
99
|
+
batch: @batch,
|
100
|
+
try_count: try_count,
|
101
|
+
report_filepath: report_filepath
|
102
|
+
}
|
103
|
+
|
104
|
+
if @reportnamer.includes_html?
|
105
|
+
html_report_filepath = File.join(@output_directory, @reportnamer.html_last_reportname)
|
106
|
+
info[:html_report_filepath] = html_report_filepath
|
107
|
+
end
|
108
|
+
if @reportnamer.includes_json?
|
109
|
+
json_report_filepath = File.join(@output_directory, @reportnamer.json_last_reportname)
|
110
|
+
info[:json_report_filepath] = json_report_filepath
|
111
|
+
end
|
112
|
+
if @result_bundle
|
113
|
+
test_result_suffix = '.test_result'
|
114
|
+
test_result_suffix.prepend("-#{@reportnamer.report_count}") unless @reportnamer.report_count.zero?
|
115
|
+
test_result_bundlepath = File.join(@output_directory, @scheme) + test_result_suffix
|
116
|
+
info[:test_result_bundlepath] = test_result_bundlepath
|
117
|
+
end
|
118
|
+
puts "interstitial about to call #{@testrun_completed_block} for batch #{@batch}"
|
119
|
+
@testrun_completed_block.call(info)
|
120
|
+
end
|
121
|
+
|
122
|
+
def set_json_env_if_necessary
|
123
|
+
if @reportnamer && @reportnamer.includes_json?
|
124
|
+
ENV['XCPRETTY_JSON_FILE_OUTPUT'] = File.join(
|
125
|
+
@output_directory,
|
126
|
+
@reportnamer.json_last_reportname
|
127
|
+
)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def finish_try(try_count)
|
132
|
+
send_info_for_try(try_count)
|
133
|
+
reset_simulators
|
134
|
+
ENV['SCAN_DERIVED_DATA_PATH'] = Dir.mktmpdir(nil, @output_directory) if @parallelize
|
135
|
+
move_test_result_bundle_for_next_run
|
136
|
+
set_json_env_if_necessary
|
137
|
+
@reportnamer && @reportnamer.increment
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module TestCenter
|
2
|
+
module Helper
|
3
|
+
module MultiScanManager
|
4
|
+
class ReportCollator
|
5
|
+
CollateJunitReportsAction = Fastlane::Actions::CollateJunitReportsAction
|
6
|
+
CollateHtmlReportsAction = Fastlane::Actions::CollateHtmlReportsAction
|
7
|
+
CollateJsonReportsAction = Fastlane::Actions::CollateJsonReportsAction
|
8
|
+
CollateTestResultBundlesAction = Fastlane::Actions::CollateTestResultBundlesAction
|
9
|
+
|
10
|
+
def initialize(params)
|
11
|
+
@source_reports_directory_glob = params[:source_reports_directory_glob]
|
12
|
+
@output_directory = params[:output_directory]
|
13
|
+
@reportnamer = params[:reportnamer]
|
14
|
+
@scheme = params[:scheme]
|
15
|
+
@result_bundle = params[:result_bundle]
|
16
|
+
@suffix = params[:suffix] || ''
|
17
|
+
end
|
18
|
+
|
19
|
+
def collate
|
20
|
+
collate_junit_reports
|
21
|
+
collate_html_reports
|
22
|
+
collate_json_reports
|
23
|
+
collate_test_result_bundles
|
24
|
+
end
|
25
|
+
|
26
|
+
def sort_globbed_files(glob)
|
27
|
+
files = Dir.glob(glob).map do |relative_filepath|
|
28
|
+
File.absolute_path(relative_filepath)
|
29
|
+
end
|
30
|
+
files.sort! { |f1, f2| File.mtime(f1) <=> File.mtime(f2) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete_globbed_intermediatefiles(glob)
|
34
|
+
retried_reportfiles = Dir.glob(glob)
|
35
|
+
FileUtils.rm_f(retried_reportfiles)
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_config(klass, options)
|
39
|
+
FastlaneCore::Configuration.create(klass.available_options, options)
|
40
|
+
end
|
41
|
+
|
42
|
+
def collate_junit_reports
|
43
|
+
glob = "#{@source_reports_directory_glob}/#{@reportnamer.junit_fileglob}"
|
44
|
+
report_files = sort_globbed_files(glob)
|
45
|
+
if report_files.size > 1
|
46
|
+
config = create_config(
|
47
|
+
CollateJunitReportsAction,
|
48
|
+
{
|
49
|
+
reports: report_files,
|
50
|
+
collated_report: File.absolute_path(File.join(@output_directory, @reportnamer.junit_reportname(@suffix)))
|
51
|
+
}
|
52
|
+
)
|
53
|
+
CollateJunitReportsAction.run(config)
|
54
|
+
delete_globbed_intermediatefiles("#{@source_reports_directory_glob}/#{@reportnamer.junit_numbered_fileglob}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def collate_html_reports
|
59
|
+
return unless @reportnamer.includes_html?
|
60
|
+
|
61
|
+
report_files = sort_globbed_files("#{@source_reports_directory_glob}/#{@reportnamer.html_fileglob}")
|
62
|
+
if report_files.size > 1
|
63
|
+
config = create_config(
|
64
|
+
CollateJunitReportsAction,
|
65
|
+
{
|
66
|
+
reports: report_files,
|
67
|
+
collated_report: File.absolute_path(File.join(@output_directory, @reportnamer.html_reportname(@suffix)))
|
68
|
+
}
|
69
|
+
)
|
70
|
+
CollateHtmlReportsAction.run(config)
|
71
|
+
delete_globbed_intermediatefiles("#{@source_reports_directory_glob}/#{@reportnamer.html_numbered_fileglob}")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def collate_json_reports
|
76
|
+
return unless @reportnamer.includes_json?
|
77
|
+
|
78
|
+
report_files = sort_globbed_files("#{@source_reports_directory_glob}/#{@reportnamer.json_fileglob}")
|
79
|
+
|
80
|
+
if report_files.size > 1
|
81
|
+
config = create_config(
|
82
|
+
CollateJsonReportsAction,
|
83
|
+
{
|
84
|
+
reports: report_files,
|
85
|
+
collated_report: File.absolute_path(File.join(@output_directory, @reportnamer.json_reportname(@suffix)))
|
86
|
+
}
|
87
|
+
)
|
88
|
+
CollateJsonReportsAction.run(config)
|
89
|
+
delete_globbed_intermediatefiles("#{@source_reports_directory_glob}/#{@reportnamer.json_numbered_fileglob}")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def collate_test_result_bundles
|
94
|
+
return unless @result_bundle
|
95
|
+
|
96
|
+
test_result_bundlepaths = sort_globbed_files("#{@source_reports_directory_glob}/#{@scheme}*.test_result")
|
97
|
+
|
98
|
+
if test_result_bundlepaths.size > 1
|
99
|
+
config = create_config(
|
100
|
+
CollateTestResultBundlesAction,
|
101
|
+
{
|
102
|
+
bundles: test_result_bundlepaths,
|
103
|
+
collated_bundle: "#{File.join(@output_directory, @scheme)}.test_result'"
|
104
|
+
}
|
105
|
+
)
|
106
|
+
CollateTestResultBundlesAction.run(config)
|
107
|
+
delete_globbed_intermediatefiles("#{@source_reports_directory_glob}/#{@scheme}-[1-9]*.test_result")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|