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.
Files changed (22) hide show
  1. checksums.yaml +5 -5
  2. data/lib/fastlane/plugin/test_center.rb +1 -1
  3. data/lib/fastlane/plugin/test_center/actions/collate_test_result_bundles.rb +1 -1
  4. data/lib/fastlane/plugin/test_center/actions/multi_scan.rb +28 -4
  5. data/lib/fastlane/plugin/test_center/actions/restart_core_simulator_service.rb +37 -0
  6. data/lib/fastlane/plugin/test_center/actions/tests_from_xctestrun.rb +1 -1
  7. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager.rb +5 -0
  8. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/device_manager.rb +26 -0
  9. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/interstitial.rb +143 -0
  10. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/report_collator.rb +113 -0
  11. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan.rb +72 -0
  12. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb +236 -0
  13. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb +272 -0
  14. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/simulator_helper.rb +59 -0
  15. data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/simulator_manager.rb +317 -0
  16. data/lib/fastlane/plugin/test_center/helper/reportname_helper.rb +15 -6
  17. data/lib/fastlane/plugin/test_center/helper/test_collector.rb +47 -3
  18. data/lib/fastlane/plugin/test_center/helper/xcodebuild_string.rb +9 -0
  19. data/lib/fastlane/plugin/test_center/helper/xctestrun_info.rb +42 -0
  20. data/lib/fastlane/plugin/test_center/version.rb +1 -1
  21. metadata +21 -12
  22. data/lib/fastlane/plugin/test_center/helper/correcting_scan_helper.rb +0 -293
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 901809f6e0b7d7470ce1ab5a04924758f8f98eac
4
- data.tar.gz: 3f1d8f95951101eb038cd3a35f84665f423bec77
2
+ SHA256:
3
+ metadata.gz: '09726aaa2b1ba6691e8738682e25aebc1dab29e1bcbb9eef7672b33fac880fec'
4
+ data.tar.gz: a970fc5ed7a5c8dd05ddd11a2b7865bcc83154469da7e0d336da5f8e6a4e7c20
5
5
  SHA512:
6
- metadata.gz: ce2ab1c98302b0bcaf9c05e743968fb3c76eff16f691dac0d33f354f0ddd8228fcd422c5f54880b4688b728cdbcf752890c9b6849a9a38450b966e998e577487
7
- data.tar.gz: f2fdd270f3bb7fc6ccdb101cb621a9dd4ff56a2fe02330a92dc3842efe636c41cfca52562422be5562cff9a691786ab8d6d5ac38697a92153f3d53302127719b
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}/*.rb', File.dirname(__FILE__))]
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
- smart_scanner = ::TestCenter::Helper::CorrectingScanHelper.new(params.values)
23
- tests_passed = smart_scanner.scan
24
- summary = run_summary(params, tests_passed, smart_scanner.retry_total_count)
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.shellescape}/#{test_identifier}"
31
+ "#{testable_name.shellsafe_testidentifier}/#{test_identifier}"
32
32
  end
33
33
  end
34
34
  tests
@@ -0,0 +1,5 @@
1
+ require_relative 'multi_scan_manager/interstitial'
2
+ require_relative 'multi_scan_manager/report_collator'
3
+ require_relative 'multi_scan_manager/simulator_manager'
4
+ require_relative 'multi_scan_manager/runner'
5
+ require_relative 'xctestrun_info'
@@ -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