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