fastlane-plugin-retry_tests 1.2.5 → 1.2.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: df60241959a25db457f558cb0ef71b6f53a3c4f8
4
- data.tar.gz: 9c6af86f03d780aec30c117ca2e9855e8723b22d
3
+ metadata.gz: 365832dd6472ba1a347a30744ed61d39c9df4290
4
+ data.tar.gz: 6e2efd77e21cec3df9f7cd06e9a3c93754c86680
5
5
  SHA512:
6
- metadata.gz: df54104e6ea929e5677fa6e5d24657ed7e6693ba327323caa0d0eb88155f32fad5bc0aa7197c56a3aeddd5af1b7ed9c9ff8c38529ddd7f4aecbc13fc9f0b2c1e
7
- data.tar.gz: 7fad586dff6e97dd66889d384ab810dec364f7d7a6a308adc9b18dd086b001f40911b9e497c2779a1a6a1aee9f37687592e8813dc781e39a92fc8ae53b0dac63
6
+ metadata.gz: f13887c8852db136a3267e4c6723226337e0cabc617fc51b5ed2d568542fef8aa812cba646dcace661d2162bc649c8e16cbc33e752ce7b006248faaa2264d972
7
+ data.tar.gz: 1cdd26937449d5a2af8f001866af143876e35a83db33550aa3e4e4bfa3769885f2c259798ccfb741d47bddc35b17c8e5ba6cad076eeef89796f3c5dce6b32035
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-retry_tests
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.5
4
+ version: 1.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gloria Chow
@@ -180,20 +180,6 @@ extra_rdoc_files: []
180
180
  files:
181
181
  - LICENSE
182
182
  - README.md
183
- - lib/fastlane/plugin/retry_tests.rb
184
- - lib/fastlane/plugin/retry_tests/actions/collate_junit_reports.rb
185
- - lib/fastlane/plugin/retry_tests/actions/multi_scan.rb
186
- - lib/fastlane/plugin/retry_tests/actions/suppress_tests.rb
187
- - lib/fastlane/plugin/retry_tests/actions/suppress_tests_from_junit.rb
188
- - lib/fastlane/plugin/retry_tests/actions/suppressed_tests.rb
189
- - lib/fastlane/plugin/retry_tests/actions/tests_from_junit.rb
190
- - lib/fastlane/plugin/retry_tests/actions/tests_from_xctestrun.rb
191
- - lib/fastlane/plugin/retry_tests/helper/correcting_scan_helper.rb
192
- - lib/fastlane/plugin/retry_tests/helper/junit_helper.rb
193
- - lib/fastlane/plugin/retry_tests/helper/reportname_helper.rb
194
- - lib/fastlane/plugin/retry_tests/helper/test_collector.rb
195
- - lib/fastlane/plugin/retry_tests/helper/xcodebuild_string.rb
196
- - lib/fastlane/plugin/retry_tests/version.rb
197
183
  homepage: https://github.com/kouzoh/fastlane-plugin-retry_tests
198
184
  licenses:
199
185
  - MIT
@@ -1,16 +0,0 @@
1
- #require 'fastlane/plugin/retry_tests/version'
2
-
3
- module Fastlane
4
- module TestCenter
5
- # Return all .rb files inside the "actions" and "helper" directory
6
- def self.all_classes
7
- Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))]
8
- end
9
- end
10
- end
11
-
12
- # By default we want to import all available actions and helpers
13
- # A plugin can contain any number of actions and plugins
14
- Fastlane::TestCenter.all_classes.each do |current|
15
- require current
16
- end
@@ -1,178 +0,0 @@
1
-
2
- require 'nokogiri'
3
- require 'nokogiri-plist'
4
- require 'FileUtils'
5
- module Fastlane
6
- module Actions
7
- class CollateJunitReportsAction < Action
8
-
9
- def self.run(params)
10
- report_filepaths = params[:reports].reverse
11
- if report_filepaths.size == 1
12
- FileUtils.cp(report_filepaths[0], params[:collated_report])
13
- else
14
- target_report = File.open(report_filepaths.shift) {|f| Nokogiri::XML(f)}
15
- file_name = params[:collated_report] + "/#{params[:scheme]}.test_result/1_Test/action_TestSummaries.plist"
16
- reports = report_filepaths.map { |report_filepath| Nokogiri::XML(Nokogiri::PList(open(report_filepath)).to_plist_xml) }
17
- reports.each do |retry_report|
18
- mergeLists(target_report, retry_report, params)
19
- end
20
- end
21
- merge_assets(params[:assets], params[:collated_report] + "/Attachments")
22
- copy_log(params[:logs], params[:collated_report] + "/")
23
- end
24
-
25
- def self.clean_report(report)
26
- report = report.gsub(/<(\w+): 0x(\w+)>/, '(\1: 0x\2)')
27
- report = report.gsub(/<XCAccessibilityElement:\/>0x(\w+)/, '(XCAccessibilityElement): 0x\1')
28
- report
29
- end
30
-
31
- def self.copy_log(log_files, logs_folder)
32
- target_log = log_files.shift
33
- FileUtils.cp_r(target_log, logs_folder)
34
- end
35
-
36
- def self.mergeLists(target_report, retry_report, params)
37
- retry_report = clean_report(retry_report)
38
- UI.verbose("Merging retried results...")
39
- Dir.mkdir(params[:collated_report]) unless File.exists?(params[:collated_report])
40
- file_name = params[:collated_report] + "/action_TestSummaries.plist"
41
- retried_tests = retry_report.xpath("//key[contains(.,'TestSummaryGUID')]/..")
42
- current_node = retried_tests.shift
43
- while (current_node != nil)
44
- testName = get_test_name(current_node)
45
- matching_node = target_report.at_xpath("//string[contains(.,'#{testName}')]/..")
46
-
47
- if (!matching_node.nil?)
48
- matching_node.previous.next.replace(current_node)
49
- write_report_to_file(target_report, file_name)
50
- end
51
- current_node = retried_tests.shift
52
- end
53
- end
54
-
55
- def self.merge_assets(asset_files, assets_folder)
56
- UI.verbose ("Merging screenshot folders...")
57
- Dir.mkdir(assets_folder) unless File.exists?(assets_folder)
58
- asset_files.each do |folder|
59
- FileUtils.cp_r(Dir[folder + '/*'], assets_folder)
60
- end
61
- end
62
-
63
- def self.merge_logs(log_files, logs_folder)
64
- UI.verbose("Merging console logs...")
65
- target_log = log_files.shift
66
- log_files.each do |log|
67
- to_append = File.read(log)
68
- File.open(target_log, "a") do |handle|
69
- handle.puts to_append
70
- end
71
- FileUtils.cp_r(target_log, logs_folder)
72
- end
73
- end
74
-
75
- def self.write_report_to_file(report, file_name)
76
- UI.verbose("Writing merged results to file...")
77
- File.new(file_name, 'w')
78
- File.open(file_name, 'w') do |f|
79
- f.write(report.to_xml)
80
- end
81
- end
82
-
83
- def self.get_test_name(test_data)
84
- test_name = test_data.xpath("(//key[contains(.,'TestSummaryGUID')])/../key[contains(.,'TestName')]/following-sibling::string").to_a[0].to_s
85
- test_name = test_name[8..-10]
86
- test_name
87
- end
88
-
89
- #####################################################
90
- # @!group Documentation
91
- #####################################################
92
-
93
- def self.description
94
- "Combines and combines tests from multiple junit report files"
95
- end
96
-
97
- def self.details
98
- "The first junit report is used as the base report. Testcases " \
99
- "from other reports are added if they do not already exist, or " \
100
- "if the testcases already exist, they are replaced." \
101
- "" \
102
- "This is done because it is assumed that fragile tests, when " \
103
- "re-run will often succeed due to less interference from other " \
104
- "tests and the subsequent junit reports will have more passed tests." \
105
- "" \
106
- "Inspired by Derek Yang's fastlane-plugin-merge_junit_report"
107
- end
108
-
109
- def self.available_options
110
- [
111
- FastlaneCore::ConfigItem.new(
112
- key: :scheme,
113
- env_name: 'SCHEME',
114
- description: 'The test scheme being run',
115
- optional: false,
116
- default_value: '',
117
- type: String
118
- ),
119
- FastlaneCore::ConfigItem.new(
120
- key: :reports,
121
- env_name: 'COLLATE_JUNIT_REPORTS_REPORTS',
122
- description: 'An array of junit reports to collate. The first report is used as the base into which other reports are merged in',
123
- optional: false,
124
- type: Array,
125
- verify_block: proc do |reports|
126
- UI.user_error!('No junit report files found') if reports.empty?
127
- reports.each do |report|
128
- UI.user_error!("Error: junit report not found: '#{report}'") unless File.exist?(report)
129
- end
130
- end
131
- ),
132
- FastlaneCore::ConfigItem.new(
133
- key: :collated_report,
134
- env_name: 'COLLATE_JUNIT_REPORTS_COLLATED_REPORT',
135
- description: 'The final junit report file where all testcases will be merged into',
136
- optional: true,
137
- default_value: 'result.xml',
138
- type: String
139
- ),
140
- FastlaneCore::ConfigItem.new(
141
- key: :assets,
142
- env_name: 'COLLATE_JUNIT_REPORTS_ASSETS',
143
- description: 'An array of junit reports to collate. The first report is used as the base into which other reports are merged in',
144
- optional: false,
145
- type: Array,
146
- verify_block: proc do |assets|
147
- UI.user_error!('No junit report files found') if assets.empty?
148
- assets.each do |asset|
149
- UI.user_error!("Error: junit report not found: '#{asset}'") unless File.exist?(asset)
150
- end
151
- end
152
- ),
153
- FastlaneCore::ConfigItem.new(
154
- key: :logs,
155
- env_name: 'COLLATE_JUNIT_REPORTS_LOGS',
156
- description: 'An array of junit reports to collate. The first report is used as the base into which other reports are merged in',
157
- optional: false,
158
- type: Array,
159
- verify_block: proc do |logs|
160
- UI.user_error!('No junit report files found') if logs.empty?
161
- logs.each do |log|
162
- UI.user_error!("Error: junit report not found: '#{log}'") unless File.exist?(log)
163
- end
164
- end
165
- )
166
- ]
167
- end
168
-
169
- def self.authors
170
- ["lyndsey-ferguson/@lyndseydf"]
171
- end
172
-
173
- def self.is_supported?(platform)
174
- platform == :ios
175
- end
176
- end
177
- end
178
- end
@@ -1,143 +0,0 @@
1
- module Fastlane
2
- module Actions
3
- require 'fastlane/actions/scan'
4
- require 'shellwords'
5
-
6
- class MultiScanAction < Action
7
- def self.run(params)
8
- try_count = 0
9
- scan_options = params.values.reject { |k| k == :try_count }
10
- final_report_path = scan_options[:result_bundle]
11
- unless Helper.test?
12
- FastlaneCore::PrintTable.print_values(
13
- config: params._values.reject { |k, v| scan_options.key?(k) },
14
- title: "Summary for multi_scan (test_center v#{Fastlane::TestCenter::VERSION})"
15
- )
16
- end
17
-
18
- scan_options = config_with_junit_report(scan_options)
19
-
20
- begin
21
- try_count += 1
22
- scan_options = config_with_retry(scan_options, try_count)
23
- config = FastlaneCore::Configuration.create(Fastlane::Actions::ScanAction.available_options, scan_options)
24
- Fastlane::Actions::ScanAction.run(config)
25
- rescue FastlaneCore::Interface::FastlaneTestFailure => e
26
- UI.verbose("Scan failed with #{e}")
27
- if try_count < params[:try_count]
28
- report_filepath = junit_report_filepath(scan_options)
29
- failed_tests = other_action.tests_from_junit(junit: report_filepath)[:failed]
30
- scan_options[:only_testing] = failed_tests.map(&:shellescape)
31
- retry
32
- end
33
- end
34
- merge_reports(scan_options, final_report_path)
35
- end
36
-
37
- def self.merge_reports(scan_options, final_report_path)
38
- folder = get_folder_root(scan_options[:output_directory])
39
- report_files = Dir.glob("#{folder}*/#{scan_options[:scheme]}.test_result/1_Test/action_TestSummaries.plist")
40
- asset_files = Dir.glob("#{folder}*/#{scan_options[:scheme]}.test_result/1_Test/Attachments")
41
- log_files = Dir.glob("#{folder}*/#{scan_options[:scheme]}.test_result/1_Test/action.xcactivitylog")
42
- #Merge all reports, screenshots, and logs if there were retried tests
43
-
44
- if report_files.size > 1
45
- other_action.collate_junit_reports(
46
- scheme: scan_options[:scheme],
47
- reports: report_files,
48
- collated_report: final_report_path,
49
- assets: asset_files,
50
- logs: log_files,
51
- )
52
- end
53
- end
54
-
55
- def self.config_has_junit_report(config)
56
- output_types = config.fetch(:output_types, '').to_s.split(',')
57
- output_filenames = config.fetch(:output_files, '').to_s.split(',')
58
-
59
- output_type_file_count_match = output_types.size == output_filenames.size
60
- output_types.include?('junit') && (output_type_file_count_match || config[:custom_report_file_name].to_s.strip.length > 0)
61
- end
62
-
63
- def self.config_with_retry(config, count)
64
- folder = get_folder_root(config[:result_bundle])
65
- config[:result_bundle] = (folder + count.to_s)
66
- config[:output_directory] = (folder + count.to_s)
67
- config
68
- end
69
-
70
- def self.get_folder_root(folder)
71
- folder = folder.gsub(/ *\d+$/, '')
72
- folder
73
- end
74
-
75
- def self.config_with_junit_report(config)
76
- return config if config_has_junit_report(config)
77
-
78
- if config[:output_types].to_s.strip.empty? || config[:custom_report_file_name]
79
- config[:custom_report_file_name] ||= 'report.xml'
80
- config[:output_types] = 'junit'
81
- elsif config[:output_types].strip == 'junit' && config[:output_files].to_s.strip.empty?
82
- config[:custom_report_file_name] ||= 'report.xml'
83
- elsif !config[:output_types].split(',').include?('junit')
84
- config[:output_types] << ',junit'
85
- config[:output_files] << ',report.xml'
86
- elsif config[:output_files].nil?
87
- config[:output_files] = config[:output_types].split(',').map { |type| "report.#{type}" }.join(',')
88
- end
89
- config
90
- end
91
-
92
- def self.junit_report_filename(config)
93
- report_filename = config[:custom_report_file_name]
94
- if report_filename.nil?
95
- junit_index = config[:output_types].split(',').find_index('junit')
96
- report_filename = config[:output_files].to_s.split(',')[junit_index]
97
- end
98
- report_filename
99
- end
100
-
101
- def self.junit_report_filepath(config)
102
- File.absolute_path(File.join(config[:output_directory], junit_report_filename(config)))
103
- end
104
-
105
- #####################################################
106
- # @!group Documentation
107
- #####################################################
108
-
109
- def self.description
110
- "Uses scan to run Xcode tests a given number of times: only re-testing failing tests."
111
- end
112
-
113
- def self.details
114
- "Use this action to run your tests if you have fragile tests that fail sporadically."
115
- end
116
-
117
- def self.scan_options
118
- ScanAction.available_options
119
- end
120
-
121
- def self.available_options
122
- scan_options + [
123
- FastlaneCore::ConfigItem.new(
124
- key: :try_count,
125
- env_name: "FL_MULTI_SCAN_TRY_COUNT",
126
- description: "The number of times to retry running tests via scan",
127
- type: Integer,
128
- is_string: false,
129
- default_value: 1
130
- )
131
- ]
132
- end
133
-
134
- def self.authors
135
- ["lyndsey-ferguson/@lyndseydf"]
136
- end
137
-
138
- def self.is_supported?(platform)
139
- platform == :ios
140
- end
141
- end
142
- end
143
- end
@@ -1,93 +0,0 @@
1
- module Fastlane
2
- module Actions
3
- class SuppressTestsAction < Action
4
- require 'xcodeproj'
5
-
6
- def self.run(params)
7
- project_path = params[:xcodeproj]
8
- all_tests_to_skip = params[:tests]
9
- scheme = params[:scheme]
10
-
11
- scheme_filepaths = Dir.glob("#{project_path}/{xcshareddata,xcuserdata}/**/xcschemes/#{scheme || '*'}.xcscheme")
12
- if scheme_filepaths.length.zero?
13
- UI.user_error!("Error: cannot find any scheme named #{scheme}") unless scheme.nil?
14
- UI.user_error!("Error: cannot find any schemes in the Xcode project")
15
- end
16
-
17
- scheme_filepaths.each do |scheme_filepath|
18
- is_dirty = false
19
- xcscheme = Xcodeproj::XCScheme.new(scheme_filepath)
20
- xcscheme.test_action.testables.each do |testable|
21
- buildable_name = File.basename(testable.buildable_references[0].buildable_name, '.xctest')
22
-
23
- tests_to_skip = all_tests_to_skip.select { |test| test.start_with?(buildable_name) }
24
- .map { |test| test.sub("#{buildable_name}/", '') }
25
-
26
- tests_to_skip.each do |test_to_skip|
27
- skipped_test = Xcodeproj::XCScheme::TestAction::TestableReference::SkippedTest.new
28
- skipped_test.identifier = test_to_skip
29
- testable.add_skipped_test(skipped_test)
30
- is_dirty = true
31
- end
32
- end
33
- xcscheme.save! if is_dirty
34
- end
35
- nil
36
- end
37
-
38
- #####################################################
39
- # @!group Documentation
40
- #####################################################
41
-
42
- def self.description
43
- "Suppresses specific tests in a specific or all Xcode Schemes in a given project"
44
- end
45
-
46
- def self.available_options
47
- [
48
- FastlaneCore::ConfigItem.new(
49
- key: :xcodeproj,
50
- env_name: "FL_SUPPRESS_TESTS_XCODE_PROJECT",
51
- description: "The file path to the Xcode project file to modify",
52
- verify_block: proc do |path|
53
- UI.user_error!("Error: Xcode project file path not given!") unless path and !path.empty?
54
- UI.user_error!("Error: Xcode project '#{path}' not found!") unless Dir.exist?(path)
55
- end
56
- ),
57
- FastlaneCore::ConfigItem.new(
58
- key: :tests,
59
- env_name: "FL_SUPPRESS_TESTS_TESTS_TO_SUPPRESS",
60
- description: "A list of tests to suppress",
61
- verify_block: proc do |tests|
62
- UI.user_error!("Error: no tests were given to suppress!") unless tests and !tests.empty?
63
- tests.each do |test_identifier|
64
- is_valid_test_identifier = %r{^[a-zA-Z][a-zA-Z0-9]+\/[a-zA-Z][a-zA-Z0-9]+(\/test[a-zA-Z0-9]+)?$} =~ test_identifier
65
- unless is_valid_test_identifier
66
- UI.user_error!("Error: invalid test identifier '#{test_identifier}'. It must be in the format of 'Testable/TestSuiteToSuppress' or 'Testable/TestSuiteToSuppress/testToSuppress'")
67
- end
68
- end
69
- end,
70
- type: Array
71
- ),
72
- FastlaneCore::ConfigItem.new(
73
- key: :scheme,
74
- optional: true,
75
- env_name: "FL_SUPPRESS_TESTS_SCHEME_TO_UPDATE",
76
- description: "The Xcode scheme where the tests should be suppressed",
77
- verify_block: proc do |scheme_name|
78
- UI.user_error!("Error: Xcode Scheme '#{scheme_name}' is not valid!") if scheme_name and scheme_name.empty?
79
- end
80
- )
81
- ]
82
- end
83
-
84
- def self.authors
85
- ["lyndsey-ferguson/@lyndseydf"]
86
- end
87
-
88
- def self.is_supported?(platform)
89
- platform == :ios
90
- end
91
- end
92
- end
93
- end
@@ -1,102 +0,0 @@
1
- module Fastlane
2
- module Actions
3
- class SuppressTestsFromJunitAction < Action
4
- def self.run(params)
5
- project_path = params[:xcodeproj]
6
- scheme = params[:scheme]
7
-
8
- scheme_filepaths = Dir.glob("#{project_path}/{xcshareddata,xcuserdata}/**/xcschemes/#{scheme || '*'}.xcscheme")
9
- if scheme_filepaths.length.zero?
10
- UI.user_error!("Error: cannot find any scheme named #{scheme}") unless scheme.nil?
11
- UI.user_error!("Error: cannot find any schemes in the Xcode project")
12
- end
13
-
14
- testables = Hash.new([])
15
- desired_passed_status = (params[:suppress_type] == :passing)
16
-
17
- report = ::TestCenter::Helper::XcodeJunit::Report.new(params[:junit])
18
-
19
- report.testables.each do |testable|
20
- testables[testable.name] = []
21
- testable.testsuites.each do |testsuite|
22
- testsuite.testcases.each do |testcase|
23
- if testcase.passed? == desired_passed_status
24
- testables[testable.name] << testcase.skipped_test
25
- end
26
- end
27
- end
28
- end
29
-
30
- scheme_filepaths.each do |scheme_filepath|
31
- is_dirty = false
32
- xcscheme = Xcodeproj::XCScheme.new(scheme_filepath)
33
-
34
- xcscheme.test_action.testables.each do |testable|
35
- buildable_name = testable.buildable_references[0].buildable_name
36
- testables[buildable_name].each do |skipped_test|
37
- testable.add_skipped_test(skipped_test)
38
- is_dirty = true
39
- end
40
- end
41
- xcscheme.save! if is_dirty
42
- end
43
- end
44
-
45
- #####################################################
46
- # @!group Documentation
47
- #####################################################
48
-
49
- def self.description
50
- "Uses a junit xml report file to suppress either passing or failing tests in an Xcode Scheme"
51
- end
52
-
53
- def self.available_options
54
- [
55
- FastlaneCore::ConfigItem.new(
56
- key: :xcodeproj,
57
- env_name: "FL_SUPPRESS_TESTS_FROM_JUNIT_XCODE_PROJECT",
58
- description: "The file path to the Xcode project file to modify",
59
- verify_block: proc do |path|
60
- UI.user_error!("Error: Xcode project file path not given!") unless path and !path.empty?
61
- UI.user_error!("Error: Xcode project '#{path}' not found!") unless Dir.exist?(path)
62
- end
63
- ),
64
- FastlaneCore::ConfigItem.new(
65
- key: :junit,
66
- env_name: "FL_SUPPRESS_TESTS_FROM_JUNIT_JUNIT_REPORT",
67
- description: "The junit xml report file from which to collect the tests to suppress",
68
- verify_block: proc do |path|
69
- UI.user_error!("Error: cannot find the junit xml report file '#{path}'") unless File.exist?(path)
70
- end
71
- ),
72
- FastlaneCore::ConfigItem.new(
73
- key: :scheme,
74
- optional: true,
75
- env_name: "FL_SUPPRESS_TESTS_FROM_JUNIT_SCHEME_TO_UPDATE",
76
- description: "The Xcode scheme where the tests should be suppressed",
77
- verify_block: proc do |scheme_name|
78
- UI.user_error!("Error: Xcode Scheme '#{scheme_name}' is not valid!") if scheme_name and scheme_name.empty?
79
- end
80
- ),
81
- FastlaneCore::ConfigItem.new(
82
- key: :suppress_type,
83
- type: Symbol,
84
- env_name: "FL_SUPPRESS_TESTS_FROM_JUNIT_SUPPRESS_TYPE",
85
- description: "Tests to suppress are either :failed or :passing",
86
- verify_block: proc do |type|
87
- UI.user_error!("Error: suppress type ':#{type}' is invalid! Only :failed or :passing are valid types") unless %i[failed passing].include?(type)
88
- end
89
- )
90
- ]
91
- end
92
-
93
- def self.authors
94
- ["lyndsey-ferguson/@lyndseydf"]
95
- end
96
-
97
- def self.is_supported?(platform)
98
- platform == :ios
99
- end
100
- end
101
- end
102
- end
@@ -1,72 +0,0 @@
1
- module Fastlane
2
- module Actions
3
- class SuppressedTestsAction < Action
4
- require 'set'
5
-
6
- def self.run(params)
7
- project_path = params[:xcodeproj]
8
- scheme = params[:scheme]
9
-
10
- scheme_filepaths = Dir.glob("#{project_path}/{xcshareddata,xcuserdata}/**/xcschemes/#{scheme || '*'}.xcscheme")
11
- if scheme_filepaths.length.zero?
12
- UI.user_error!("Error: cannot find any scheme named #{scheme}") unless scheme.nil?
13
- UI.user_error!("Error: cannot find any schemes in the Xcode project")
14
- end
15
-
16
- skipped_tests = Set.new
17
- scheme_filepaths.each do |scheme_filepath|
18
- xcscheme = Xcodeproj::XCScheme.new(scheme_filepath)
19
- xcscheme.test_action.testables.each do |testable|
20
- buildable_name = testable.buildable_references[0]
21
- .buildable_name
22
-
23
- buildable_name = File.basename(buildable_name, '.xctest')
24
- testable.skipped_tests.map do |skipped_test|
25
- skipped_tests.add("#{buildable_name}/#{skipped_test.identifier}")
26
- end
27
- end
28
- end
29
- skipped_tests.to_a
30
- end
31
-
32
- #####################################################
33
- # @!group Documentation
34
- #####################################################
35
-
36
- def self.description
37
- "Retrieves a list of tests that are suppressed in a specific or all Xcode Schemes in a project"
38
- end
39
-
40
- def self.available_options
41
- [
42
- FastlaneCore::ConfigItem.new(
43
- key: :xcodeproj,
44
- env_name: "FL_SUPPRESSED_TESTS_XCODE_PROJECT",
45
- description: "The file path to the Xcode project file to read the skipped tests from",
46
- verify_block: proc do |path|
47
- UI.user_error!("Error: Xcode project file path not given!") unless path and !path.empty?
48
- UI.user_error!("Error: Xcode project '#{path}' not found!") unless Dir.exist?(path)
49
- end
50
- ),
51
- FastlaneCore::ConfigItem.new(
52
- key: :scheme,
53
- optional: true,
54
- env_name: "FL_SUPPRESSED_TESTS_SCHEME_TO_UPDATE",
55
- description: "The Xcode scheme where the suppressed tests may be",
56
- verify_block: proc do |scheme_name|
57
- UI.user_error!("Error: Xcode Scheme '#{scheme_name}' is not valid!") if scheme_name and scheme_name.empty?
58
- end
59
- )
60
- ]
61
- end
62
-
63
- def self.authors
64
- ["lyndsey-ferguson/@lyndseydf"]
65
- end
66
-
67
- def self.is_supported?(platform)
68
- platform == :ios
69
- end
70
- end
71
- end
72
- end
@@ -1,59 +0,0 @@
1
- module Fastlane
2
- module Actions
3
- class TestsFromJunitAction < Action
4
- def self.run(params)
5
- report = ::TestCenter::Helper::XcodeJunit::Report.new(params[:junit])
6
- passing = []
7
- failed = []
8
- report.testables.each do |testable|
9
- testable.testsuites.each do |testsuite|
10
- testsuite.testcases.each do |testcase|
11
- if testcase.passed?
12
- passing << testcase.identifier
13
- else
14
- failed << testcase.identifier
15
- end
16
- end
17
- end
18
- end
19
- {
20
- failed: failed,
21
- passing: passing
22
- }
23
- end
24
-
25
- #####################################################
26
- # @!group Documentation
27
- #####################################################
28
-
29
- def self.description
30
- "Retrieves the failing and passing tests as reported in a junit xml file"
31
- end
32
-
33
- def self.available_options
34
- [
35
- FastlaneCore::ConfigItem.new(
36
- key: :junit,
37
- env_name: "FL_SUPPRESS_TESTS_FROM_JUNIT_JUNIT_REPORT", # The name of the environment variable
38
- description: "The junit xml report file from which to collect the tests to suppress",
39
- verify_block: proc do |path|
40
- UI.user_error!("Error: cannot find the junit xml report file '#{path}'") unless File.exist?(path)
41
- end
42
- )
43
- ]
44
- end
45
-
46
- def self.return_value
47
- "A Hash with an Array of :passing and :failed tests"
48
- end
49
-
50
- def self.authors
51
- ["lyndsey-ferguson/lyndseydf"]
52
- end
53
-
54
- def self.is_supported?(platform)
55
- platform == :ios
56
- end
57
- end
58
- end
59
- end
@@ -1,65 +0,0 @@
1
- require 'plist'
2
-
3
- module Fastlane
4
- module Actions
5
- class TestsFromXctestrunAction < Action
6
- def self.run(params)
7
- return xctestrun_tests(params[:xctestrun])
8
- end
9
-
10
- def self.xctestrun_tests(xctestrun_path)
11
- xctestrun = Plist.parse_xml(xctestrun_path)
12
- xctestrun_rootpath = File.dirname(xctestrun_path)
13
- tests = Hash.new([])
14
- xctestrun.each do |testable_name, xctestrun_config|
15
- test_identifiers = XCTestList.tests(xctest_bundle_path(xctestrun_rootpath, xctestrun_config))
16
- if xctestrun_config.key?('SkipTestIdentifiers')
17
- test_identifiers.reject! { |test_identifier| xctestrun_config['SkipTestIdentifiers'].include?(test_identifier) }
18
- end
19
- tests[testable_name] = test_identifiers.map do |test_identifier|
20
- "#{testable_name.shellescape}/#{test_identifier}"
21
- end
22
- end
23
- tests
24
- end
25
-
26
- def self.xctest_bundle_path(xctestrun_rootpath, xctestrun_config)
27
- xctest_host_path = xctestrun_config['TestHostPath'].sub('__TESTROOT__', xctestrun_rootpath)
28
- xctestrun_config['TestBundlePath'].sub('__TESTHOST__', xctest_host_path).sub('__TESTROOT__', xctestrun_rootpath)
29
- end
30
-
31
- #####################################################
32
- # @!group Documentation
33
- #####################################################
34
-
35
- def self.description
36
- "Retrieves all of the tests from xctest bundles referenced by the xctestrun file"
37
- end
38
-
39
- def self.available_options
40
- [
41
- FastlaneCore::ConfigItem.new(
42
- key: :xctestrun,
43
- env_name: "FL_SUPPRESS_TESTS_FROM_XCTESTRUN_FILE",
44
- description: "The xctestrun file to use to find where the xctest bundle file is for test retrieval",
45
- verify_block: proc do |path|
46
- UI.user_error!("Error: cannot find the xctestrun file '#{path}'") unless File.exist?(path)
47
- end
48
- )
49
- ]
50
- end
51
-
52
- def self.return_value
53
- "A Hash of testable => tests, where testable is the name of the test target and tests is an array of test identifiers"
54
- end
55
-
56
- def self.authors
57
- ["lyndsey-ferguson/lyndseydf"]
58
- end
59
-
60
- def self.is_supported?(platform)
61
- platform == :ios
62
- end
63
- end
64
- end
65
- end
@@ -1,164 +0,0 @@
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
- clean
24
- try_count
25
- batch_count
26
- custom_report_file_name
27
- fail_build
28
- testrun_completed_block
29
- ].include?(option)
30
- end
31
- @test_collector = TestCollector.new(multi_scan_options)
32
- end
33
-
34
- def scan
35
- tests_passed = true
36
- @testables_count = @test_collector.testables.size
37
- @test_collector.testables.each do |testable|
38
- tests_passed = scan_testable(testable) && tests_passed
39
- end
40
- tests_passed
41
- end
42
-
43
- def scan_testable(testable)
44
- tests_passed = true
45
- reportnamer = ReportNameHelper.new(
46
- @given_output_types,
47
- @given_output_files,
48
- @given_custom_report_file_name
49
- )
50
- output_directory = @output_directory
51
- testable_tests = @test_collector.testables_tests[testable]
52
- if @batch_count > 1 || @testables_count > 1
53
- current_batch = 1
54
- testable_tests.each_slice((testable_tests.length / @batch_count.to_f).round).to_a.each do |tests_batch|
55
- if @testables_count > 1
56
- output_directory = File.join(@output_directory, "results-#{testable}")
57
- end
58
- FastlaneCore::UI.header("Starting test run on testable '#{testable}'")
59
- tests_passed = correcting_scan(
60
- {
61
- only_testing: tests_batch,
62
- output_directory: output_directory
63
- },
64
- current_batch,
65
- reportnamer
66
- ) && tests_passed
67
- current_batch += 1
68
- reportnamer.increment
69
- end
70
- else
71
- options = {
72
- output_directory: output_directory,
73
- only_testing: testable_tests
74
- }
75
- tests_passed = correcting_scan(options, 1, reportnamer) && tests_passed
76
- end
77
- collate_reports(output_directory, reportnamer)
78
- tests_passed
79
- end
80
-
81
- def collate_reports(output_directory, reportnamer)
82
- report_files = Dir.glob("#{output_directory}/*#{reportnamer.junit_filextension}").map do |relative_filepath|
83
- File.absolute_path(relative_filepath)
84
- end
85
- if report_files.size > 1
86
- config = FastlaneCore::Configuration.create(
87
- Fastlane::Actions::CollateJunitReportsAction.available_options,
88
- {
89
- reports: report_files.sort { |f1, f2| File.mtime(f1) <=> File.mtime(f2) },
90
- collated_report: File.absolute_path(File.join(output_directory, reportnamer.junit_reportname))
91
- }
92
- )
93
- Fastlane::Actions::CollateJunitReportsAction.run(config)
94
- end
95
- retried_junit_reportfiles = Dir.glob("#{output_directory}/**/*-[1-9]*#{reportnamer.junit_filextension}")
96
- FileUtils.rm_f(retried_junit_reportfiles)
97
- end
98
-
99
- def correcting_scan(scan_run_options, batch, reportnamer)
100
- scan_options = @scan_options.merge(scan_run_options)
101
- try_count = 0
102
- tests_passed = true
103
- begin
104
- try_count += 1
105
- config = FastlaneCore::Configuration.create(
106
- Fastlane::Actions::ScanAction.available_options,
107
- scan_options.merge(reportnamer.scan_options)
108
- )
109
- quit_simulators
110
- Fastlane::Actions::ScanAction.run(config)
111
- @testrun_completed_block && @testrun_completed_block.call(
112
- testrun_info(batch, try_count, reportnamer, scan_options[:output_directory])
113
- )
114
- tests_passed = true
115
- rescue FastlaneCore::Interface::FastlaneTestFailure => e
116
- FastlaneCore::UI.verbose("Scan failed with #{e}")
117
- if try_count < @try_count
118
- @retry_total_count += 1
119
-
120
- info = testrun_info(batch, try_count, reportnamer, scan_options[:output_directory])
121
- @testrun_completed_block && @testrun_completed_block.call(
122
- info
123
- )
124
- scan_options[:only_testing] = info[:failed].map(&:shellescape)
125
- FastlaneCore::UI.message('Re-running scan on only failed tests')
126
- reportnamer.increment
127
- retry
128
- end
129
- tests_passed = false
130
- end
131
- tests_passed
132
- end
133
-
134
- def testrun_info(batch, try_count, reportnamer, output_directory)
135
- report_filepath = File.join(output_directory, reportnamer.junit_last_reportname)
136
- config = FastlaneCore::Configuration.create(
137
- Fastlane::Actions::TestsFromJunitAction.available_options,
138
- {
139
- junit: File.absolute_path(report_filepath)
140
- }
141
- )
142
- junit_results = Fastlane::Actions::TestsFromJunitAction.run(config)
143
-
144
- {
145
- failed: junit_results[:failed],
146
- passing: junit_results[:passing],
147
- batch: batch,
148
- try_count: try_count,
149
- report_filepath: report_filepath
150
- }
151
- end
152
-
153
- def quit_simulators
154
- Fastlane::Actions.sh("killall -9 'iPhone Simulator' 'Simulator' 'SimulatorBridge' &> /dev/null || true", log: false)
155
- launchctl_list_count = 0
156
- while Fastlane::Actions.sh('launchctl list | grep com.apple.CoreSimulator.CoreSimulatorService || true', log: false) != ''
157
- break if (launchctl_list_count += 1) > 10
158
- Fastlane::Actions.sh('launchctl remove com.apple.CoreSimulator.CoreSimulatorService &> /dev/null || true', log: false)
159
- sleep(1)
160
- end
161
- end
162
- end
163
- end
164
- end
@@ -1,94 +0,0 @@
1
- require_relative 'xcodebuild_string'
2
-
3
- module TestCenter
4
- module Helper
5
- module XcodeJunit
6
- require 'xcodeproj'
7
-
8
- class Report
9
- def initialize(junit_report_filepath)
10
- report_file = File.open(junit_report_filepath) { |f| REXML::Document.new(f) }
11
- UI.user_error!("Malformed XML test report file given") if report_file.root.nil?
12
- UI.user_error!("Valid XML file is not an Xcode test report") if report_file.get_elements('testsuites').empty?
13
-
14
- @testables = []
15
- report_file.elements.each('testsuites') do |testsuites_element|
16
- @testables << Testable.new(testsuites_element)
17
- end
18
- end
19
-
20
- def testables
21
- return @testables
22
- end
23
- end
24
-
25
- class Testable
26
- def initialize(xml_element)
27
- @root = xml_element
28
- @testsuites = []
29
- @root.elements.each('testsuite') do |testsuite_element|
30
- @testsuites << TestSuite.new(testsuite_element)
31
- end
32
- end
33
-
34
- def name
35
- return @root.attributes['name']
36
- end
37
-
38
- def testsuites
39
- return @testsuites
40
- end
41
- end
42
-
43
- class TestSuite
44
- def initialize(xml_element)
45
- @root = xml_element
46
- @testcases = []
47
- @root.elements.each('testcase') do |testcase_element|
48
- @testcases << TestCase.new(testcase_element)
49
- end
50
- end
51
-
52
- def name
53
- return @root.attributes['name']
54
- end
55
-
56
- def identifier
57
- name.testsuite
58
- end
59
-
60
- def is_swift?
61
- return name.include?('.')
62
- end
63
-
64
- def testcases
65
- return @testcases
66
- end
67
- end
68
-
69
- class TestCase
70
- attr_reader :identifier
71
- attr_reader :skipped_test
72
-
73
- def initialize(xml_element)
74
- @root = xml_element
75
- name = xml_element.attributes['name']
76
- full_testsuite = xml_element.parent.attributes['name']
77
- testsuite = full_testsuite.testsuite
78
- is_swift = full_testsuite.testsuite_swift?
79
-
80
- testable_filename = xml_element.parent.parent.attributes['name']
81
- testable = File.basename(testable_filename, '.xctest')
82
- @identifier = "#{testable}/#{testsuite}/#{name}"
83
- @skipped_test = Xcodeproj::XCScheme::TestAction::TestableReference::SkippedTest.new
84
- @skipped_test.identifier = "#{testsuite}/#{name}#{'()' if is_swift}"
85
- @passed = xml_element.get_elements('failure').size.zero?
86
- end
87
-
88
- def passed?
89
- @passed
90
- end
91
- end
92
- end
93
- end
94
- end
@@ -1,70 +0,0 @@
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
@@ -1,60 +0,0 @@
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
@@ -1,14 +0,0 @@
1
-
2
- class String
3
- def testsuite_swift?
4
- self.include?('.')
5
- end
6
-
7
- def testsuite
8
- if self.testsuite_swift?
9
- self.split('.')[1]
10
- else
11
- self
12
- end
13
- end
14
- end
@@ -1,5 +0,0 @@
1
- module Fastlane
2
- module TestCenter
3
- VERSION = "3.0.6"
4
- end
5
- end