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