fastlane 2.198.0 → 2.201.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +88 -88
  4. data/deliver/lib/deliver/app_screenshot.rb +8 -0
  5. data/deliver/lib/deliver/app_screenshot_iterator.rb +1 -1
  6. data/fastlane/lib/.DS_Store +0 -0
  7. data/fastlane/lib/fastlane/.DS_Store +0 -0
  8. data/fastlane/lib/fastlane/actions/.DS_Store +0 -0
  9. data/fastlane/lib/fastlane/actions/download_dsyms.rb +26 -27
  10. data/fastlane/lib/fastlane/actions/ensure_xcode_version.rb +1 -1
  11. data/fastlane/lib/fastlane/actions/get_version_number.rb +1 -0
  12. data/fastlane/lib/fastlane/actions/trainer.rb +49 -0
  13. data/fastlane/lib/fastlane/actions/update_code_signing_settings.rb +18 -1
  14. data/fastlane/lib/fastlane/actions/xcversion.rb +18 -3
  15. data/fastlane/lib/fastlane/documentation/docs_generator.rb +17 -12
  16. data/fastlane/lib/fastlane/helper/xcodebuild_formatter_helper.rb +9 -0
  17. data/fastlane/lib/fastlane/tools.rb +2 -1
  18. data/fastlane/lib/fastlane/version.rb +1 -1
  19. data/fastlane/swift/Actions.swift +1 -1
  20. data/fastlane/swift/Appfile.swift +1 -1
  21. data/fastlane/swift/ArgumentProcessor.swift +1 -1
  22. data/fastlane/swift/ControlCommand.swift +1 -1
  23. data/fastlane/swift/Deliverfile.swift +2 -2
  24. data/fastlane/swift/DeliverfileProtocol.swift +2 -2
  25. data/fastlane/swift/Fastlane.swift +44 -15
  26. data/fastlane/swift/Gymfile.swift +2 -2
  27. data/fastlane/swift/GymfileProtocol.swift +6 -2
  28. data/fastlane/swift/LaneFileProtocol.swift +1 -1
  29. data/fastlane/swift/MainProcess.swift +1 -1
  30. data/fastlane/swift/Matchfile.swift +2 -2
  31. data/fastlane/swift/MatchfileProtocol.swift +2 -2
  32. data/fastlane/swift/OptionalConfigValue.swift +1 -1
  33. data/fastlane/swift/Plugins.swift +1 -1
  34. data/fastlane/swift/Precheckfile.swift +2 -2
  35. data/fastlane/swift/PrecheckfileProtocol.swift +2 -2
  36. data/fastlane/swift/RubyCommand.swift +1 -1
  37. data/fastlane/swift/RubyCommandable.swift +1 -1
  38. data/fastlane/swift/Runner.swift +4 -8
  39. data/fastlane/swift/RunnerArgument.swift +1 -1
  40. data/fastlane/swift/Scanfile.swift +2 -2
  41. data/fastlane/swift/ScanfileProtocol.swift +4 -4
  42. data/fastlane/swift/Screengrabfile.swift +2 -2
  43. data/fastlane/swift/ScreengrabfileProtocol.swift +2 -2
  44. data/fastlane/swift/Snapshotfile.swift +2 -2
  45. data/fastlane/swift/SnapshotfileProtocol.swift +2 -2
  46. data/fastlane/swift/SocketClient.swift +1 -1
  47. data/fastlane/swift/SocketClientDelegateProtocol.swift +1 -1
  48. data/fastlane/swift/SocketResponse.swift +1 -1
  49. data/fastlane/swift/formatting/Brewfile.lock.json +14 -14
  50. data/fastlane/swift/main.swift +1 -1
  51. data/fastlane_core/lib/fastlane_core/ipa_file_analyser.rb +10 -5
  52. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +15 -5
  53. data/gym/lib/gym/generators/build_command_generator.rb +69 -23
  54. data/gym/lib/gym/options.rb +23 -5
  55. data/scan/lib/scan/options.rb +27 -7
  56. data/scan/lib/scan/runner.rb +58 -15
  57. data/scan/lib/scan/test_command_generator.rb +54 -5
  58. data/snapshot/lib/snapshot/options.rb +23 -7
  59. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +1 -1
  60. data/snapshot/lib/snapshot/test_command_generator.rb +37 -2
  61. data/spaceship/lib/spaceship/client.rb +35 -15
  62. data/spaceship/lib/spaceship/commands_generator.rb +1 -1
  63. data/spaceship/lib/spaceship/connect_api/models/app.rb +43 -0
  64. data/spaceship/lib/spaceship/connect_api/models/app_info.rb +1 -0
  65. data/spaceship/lib/spaceship/connect_api/models/app_screenshot_set.rb +2 -0
  66. data/spaceship/lib/spaceship/connect_api/models/review_submission.rb +73 -0
  67. data/spaceship/lib/spaceship/connect_api/models/review_submission_item.rb +40 -0
  68. data/spaceship/lib/spaceship/connect_api/response.rb +13 -0
  69. data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +83 -0
  70. data/spaceship/lib/spaceship/connect_api.rb +2 -0
  71. data/spaceship/lib/spaceship/globals.rb +9 -0
  72. data/spaceship/lib/spaceship/spaceauth_runner.rb +1 -1
  73. data/supply/lib/supply/options.rb +8 -0
  74. data/supply/lib/supply/uploader.rb +6 -2
  75. data/trainer/lib/.DS_Store +0 -0
  76. data/trainer/lib/assets/junit.xml.erb +20 -0
  77. data/trainer/lib/trainer/commands_generator.rb +51 -0
  78. data/trainer/lib/trainer/junit_generator.rb +31 -0
  79. data/trainer/lib/trainer/module.rb +10 -0
  80. data/trainer/lib/trainer/options.rb +55 -0
  81. data/trainer/lib/trainer/test_parser.rb +335 -0
  82. data/trainer/lib/trainer/xcresult.rb +403 -0
  83. data/trainer/lib/trainer.rb +7 -0
  84. metadata +38 -21
@@ -217,6 +217,14 @@ module Supply
217
217
  verify_block: proc do |value|
218
218
  UI.user_error!("'rollout' is no longer a valid track name - please use 'production' instead") if value.casecmp('rollout').zero?
219
219
  end),
220
+ FastlaneCore::ConfigItem.new(key: :track_promote_release_status,
221
+ env_name: "SUPPLY_TRACK_PROMOTE_RELEASE_STATUS",
222
+ optional: true,
223
+ description: "Promoted track release status (used when promoting a track) - valid values are #{Supply::ReleaseStatus::ALL.join(', ')}",
224
+ default_value: Supply::ReleaseStatus::COMPLETED,
225
+ verify_block: proc do |value|
226
+ UI.user_error!("Value must be one of '#{Supply::RELEASE_STATUS}'") unless Supply::ReleaseStatus::ALL.include?(value)
227
+ end),
220
228
  FastlaneCore::ConfigItem.new(key: :validate_only,
221
229
  env_name: "SUPPLY_VALIDATE_ONLY",
222
230
  optional: true,
@@ -164,6 +164,10 @@ module Supply
164
164
  UI.user_error!(%(Cannot specify rollout percentage when the release status is set to 'draft'))
165
165
  end
166
166
 
167
+ if Supply.config[:track_promote_release_status] == Supply::ReleaseStatus::DRAFT && Supply.config[:rollout]
168
+ UI.user_error!(%(Cannot specify rollout percentage when the track promote release status is set to 'draft'))
169
+ end
170
+
167
171
  unless Supply.config[:version_codes_to_retain].nil?
168
172
  Supply.config[:version_codes_to_retain] = Supply.config[:version_codes_to_retain].map(&:to_i)
169
173
  end
@@ -182,7 +186,7 @@ module Supply
182
186
  end
183
187
  else
184
188
  releases = releases.select do |release|
185
- release.status == Supply::ReleaseStatus::COMPLETED
189
+ release.status == Supply.config[:release_status]
186
190
  end
187
191
  end
188
192
 
@@ -200,7 +204,7 @@ module Supply
200
204
  release.status = Supply::ReleaseStatus::IN_PROGRESS
201
205
  release.user_fraction = rollout
202
206
  else
203
- release.status = Supply::ReleaseStatus::COMPLETED
207
+ release.status = Supply.config[:track_promote_release_status]
204
208
  release.user_fraction = nil
205
209
  end
206
210
 
Binary file
@@ -0,0 +1,20 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <% number_of_tests = 0 %>
3
+ <% number_of_failures = 0 %>
4
+ <% @results.each { |a| number_of_tests += a[:number_of_tests] } %>
5
+ <% @results.each { |a| number_of_failures += a[:number_of_failures] } %>
6
+
7
+ <testsuites tests="<%= number_of_tests %>" failures="<%= number_of_failures %>">
8
+ <% @results.each do |testsuite| %>
9
+ <testsuite name=<%= (testsuite[:target_name].nil? ? testsuite[:test_name] : testsuite[:target_name]).encode(:xml => :attr) %> tests="<%= testsuite[:number_of_tests] %>" failures="<%= testsuite[:number_of_failures] %>" time="<%= testsuite[:duration] %>">
10
+ <% testsuite[:tests].each do |test| %>
11
+ <testcase classname=<%= test[:test_group].encode(:xml => :attr) %> name=<%= test[:name].encode(:xml => :attr) %> time="<%= test[:duration] %>">
12
+ <% (test[:failures] || []).each do |failure| %>
13
+ <failure message=<%= failure[:failure_message].encode(:xml => :attr) %>>
14
+ </failure>
15
+ <% end %>
16
+ </testcase>
17
+ <% end %>
18
+ </testsuite>
19
+ <% end %>
20
+ </testsuites>
@@ -0,0 +1,51 @@
1
+ require 'commander'
2
+
3
+ require 'fastlane_core/configuration/configuration'
4
+ require 'fastlane_core/ui/help_formatter'
5
+
6
+ require_relative 'options'
7
+ require_relative 'test_parser'
8
+
9
+ require_relative 'module'
10
+
11
+ HighLine.track_eof = false
12
+
13
+ module Trainer
14
+ class CommandsGenerator
15
+ include Commander::Methods
16
+
17
+ def self.start
18
+ self.new.run
19
+ end
20
+
21
+ def run
22
+ program :version, Fastlane::VERSION
23
+ program :description, Trainer::DESCRIPTION
24
+ program :help, 'Author', 'Felix Krause <trainer@krausefx.com>'
25
+ program :help, 'Website', 'https://fastlane.tools'
26
+ program :help, 'GitHub', 'https://github.com/KrauseFx/trainer'
27
+ program :help_formatter, :compact
28
+
29
+ global_option('--verbose', 'Shows a more verbose output') { $verbose = true }
30
+
31
+ always_trace!
32
+
33
+ FastlaneCore::CommanderGenerator.new.generate(Trainer::Options.available_options)
34
+
35
+ command :run do |c|
36
+ c.syntax = 'trainer'
37
+ c.description = Trainer::DESCRIPTION
38
+
39
+ c.action do |args, options|
40
+ options = FastlaneCore::Configuration.create(Trainer::Options.available_options, options.__hash__)
41
+ FastlaneCore::PrintTable.print_values(config: options, title: "Summary for trainer #{Fastlane::VERSION}") if $verbose
42
+ Trainer::TestParser.auto_convert(options)
43
+ end
44
+ end
45
+
46
+ default_command(:run)
47
+
48
+ run!
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'module'
2
+
3
+ module Trainer
4
+ class JunitGenerator
5
+ attr_accessor :results
6
+
7
+ def initialize(results)
8
+ self.results = results
9
+ end
10
+
11
+ def generate
12
+ # JUnit file documentation: http://llg.cubic.org/docs/junit/
13
+ # And http://nelsonwells.net/2012/09/how-jenkins-ci-parses-and-displays-junit-output/
14
+ # And http://windyroad.com.au/dl/Open%20Source/JUnit.xsd
15
+
16
+ lib_path = Trainer::ROOT
17
+ xml_path = File.join(lib_path, "lib/assets/junit.xml.erb")
18
+ xml = ERB.new(File.read(xml_path), nil, '<>').result(binding) # http://www.rrn.dk/rubys-erb-templating-system
19
+
20
+ xml = xml.gsub('system_', 'system-').delete("\e") # Jenkins can not parse 'ESC' symbol
21
+
22
+ # We have to manuall clear empty lines
23
+ # They may contain white spaces
24
+ clean_xml = []
25
+ xml.each_line do |row|
26
+ clean_xml << row.delete("\n") if row.strip.to_s.length > 0
27
+ end
28
+ return clean_xml.join("\n")
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,10 @@
1
+ require 'fastlane_core/helper'
2
+ require 'fastlane/boolean'
3
+
4
+ module Trainer
5
+ Helper = FastlaneCore::Helper # you gotta love Ruby: Helper.* should use the Helper class contained in FastlaneCore
6
+ UI = FastlaneCore::UI
7
+ Boolean = Fastlane::Boolean
8
+ ROOT = Pathname.new(File.expand_path('../../..', __FILE__))
9
+ DESCRIPTION = "Convert xcodebuild plist and xcresult files to JUnit reports"
10
+ end
@@ -0,0 +1,55 @@
1
+ require 'fastlane_core/configuration/config_item'
2
+
3
+ require_relative 'module'
4
+
5
+ module Trainer
6
+ class Options
7
+ def self.available_options
8
+ @options ||= [
9
+ FastlaneCore::ConfigItem.new(key: :path,
10
+ short_option: "-p",
11
+ env_name: "TRAINER_PATH",
12
+ default_value: ".",
13
+ description: "Path to the directory that should be converted",
14
+ verify_block: proc do |value|
15
+ v = File.expand_path(value.to_s)
16
+ if v.end_with?(".plist")
17
+ UI.user_error!("Can't find file at path #{v}") unless File.exist?(v)
18
+ else
19
+ UI.user_error!("Path '#{v}' is not a directory or can't be found") unless File.directory?(v)
20
+ end
21
+ end),
22
+ FastlaneCore::ConfigItem.new(key: :extension,
23
+ short_option: "-e",
24
+ env_name: "TRAINER_EXTENSION",
25
+ default_value: ".xml",
26
+ description: "The extension for the newly created file. Usually .xml or .junit",
27
+ verify_block: proc do |value|
28
+ UI.user_error!("extension must contain a `.`") unless value.include?(".")
29
+ end),
30
+ FastlaneCore::ConfigItem.new(key: :output_directory,
31
+ short_option: "-o",
32
+ env_name: "TRAINER_OUTPUT_DIRECTORY",
33
+ default_value: nil,
34
+ optional: true,
35
+ description: "Directoy in which the xml files should be written to. Same directory as source by default"),
36
+ FastlaneCore::ConfigItem.new(key: :fail_build,
37
+ env_name: "TRAINER_FAIL_BUILD",
38
+ description: "Should this step stop the build if the tests fail? Set this to false if you're handling this with a test reporter",
39
+ is_string: false,
40
+ default_value: true),
41
+ FastlaneCore::ConfigItem.new(key: :xcpretty_naming,
42
+ short_option: "-x",
43
+ env_name: "TRAINER_XCPRETTY_NAMING",
44
+ description: "Produces class name and test name identical to xcpretty naming in junit file",
45
+ is_string: false,
46
+ default_value: false),
47
+ FastlaneCore::ConfigItem.new(key: :silent,
48
+ env_name: "TRAINER_SILENT",
49
+ description: "Silences all output",
50
+ is_string: false,
51
+ default_value: false)
52
+ ]
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,335 @@
1
+ require 'plist'
2
+
3
+ require 'fastlane_core/print_table'
4
+
5
+ require_relative 'junit_generator'
6
+ require_relative 'xcresult'
7
+ require_relative 'module'
8
+
9
+ module Trainer
10
+ class TestParser
11
+ attr_accessor :data
12
+
13
+ attr_accessor :file_content
14
+
15
+ attr_accessor :raw_json
16
+
17
+ attr_accessor :number_of_tests
18
+ attr_accessor :number_of_failures
19
+ attr_accessor :number_of_tests_excluding_retries
20
+ attr_accessor :number_of_failures_excluding_retries
21
+ attr_accessor :number_of_retries
22
+
23
+ # Returns a hash with the path being the key, and the value
24
+ # defining if the tests were successful
25
+ def self.auto_convert(config)
26
+ unless config[:silent]
27
+ FastlaneCore::PrintTable.print_values(config: config,
28
+ title: "Summary for trainer #{Fastlane::VERSION}")
29
+ end
30
+
31
+ containing_dir = config[:path]
32
+ # Xcode < 10
33
+ files = Dir["#{containing_dir}/**/Logs/Test/*TestSummaries.plist"]
34
+ files += Dir["#{containing_dir}/Test/*TestSummaries.plist"]
35
+ files += Dir["#{containing_dir}/*TestSummaries.plist"]
36
+ # Xcode 10
37
+ files += Dir["#{containing_dir}/**/Logs/Test/*.xcresult/TestSummaries.plist"]
38
+ files += Dir["#{containing_dir}/Test/*.xcresult/TestSummaries.plist"]
39
+ files += Dir["#{containing_dir}/*.xcresult/TestSummaries.plist"]
40
+ files += Dir[containing_dir] if containing_dir.end_with?(".plist") # if it's the exact path to a plist file
41
+ # Xcode 11
42
+ files += Dir["#{containing_dir}/**/Logs/Test/*.xcresult"]
43
+ files += Dir["#{containing_dir}/Test/*.xcresult"]
44
+ files += Dir["#{containing_dir}/*.xcresult"]
45
+ files << containing_dir if File.extname(containing_dir) == ".xcresult"
46
+
47
+ if files.empty?
48
+ UI.user_error!("No test result files found in directory '#{containing_dir}', make sure the file name ends with 'TestSummaries.plist' or '.xcresult'")
49
+ end
50
+
51
+ return_hash = {}
52
+ files.each do |path|
53
+ if config[:output_directory]
54
+ FileUtils.mkdir_p(config[:output_directory])
55
+ # Remove .xcresult or .plist extension
56
+ if path.end_with?(".xcresult")
57
+ filename = File.basename(path).gsub(".xcresult", config[:extension])
58
+ else
59
+ filename = File.basename(path).gsub(".plist", config[:extension])
60
+ end
61
+ to_path = File.join(config[:output_directory], filename)
62
+ else
63
+ # Remove .xcresult or .plist extension
64
+ if path.end_with?(".xcresult")
65
+ to_path = path.gsub(".xcresult", config[:extension])
66
+ else
67
+ to_path = path.gsub(".plist", config[:extension])
68
+ end
69
+ end
70
+
71
+ tp = Trainer::TestParser.new(path, config)
72
+ File.write(to_path, tp.to_junit)
73
+ UI.success("Successfully generated '#{to_path}'") unless config[:silent]
74
+
75
+ return_hash[to_path] = {
76
+ successful: tp.tests_successful?,
77
+ number_of_tests: tp.number_of_tests,
78
+ number_of_failures: tp.number_of_failures,
79
+ number_of_tests_excluding_retries: tp.number_of_tests_excluding_retries,
80
+ number_of_failures_excluding_retries: tp.number_of_failures_excluding_retries,
81
+ number_of_retries: tp.number_of_retries
82
+ }
83
+ end
84
+ return_hash
85
+ end
86
+
87
+ def initialize(path, config = {})
88
+ path = File.expand_path(path)
89
+ UI.user_error!("File not found at path '#{path}'") unless File.exist?(path)
90
+
91
+ if File.directory?(path) && path.end_with?(".xcresult")
92
+ parse_xcresult(path)
93
+ else
94
+ self.file_content = File.read(path)
95
+ self.raw_json = Plist.parse_xml(self.file_content)
96
+
97
+ return if self.raw_json["FormatVersion"].to_s.length.zero? # maybe that's a useless plist file
98
+
99
+ ensure_file_valid!
100
+ parse_content(config[:xcpretty_naming])
101
+ end
102
+
103
+ self.number_of_tests = 0
104
+ self.number_of_failures = 0
105
+ self.number_of_tests_excluding_retries = 0
106
+ self.number_of_failures_excluding_retries = 0
107
+ self.number_of_retries = 0
108
+ self.data.each do |thing|
109
+ self.number_of_tests += thing[:number_of_tests].to_i
110
+ self.number_of_failures += thing[:number_of_failures].to_i
111
+ self.number_of_tests_excluding_retries += thing[:number_of_tests_excluding_retries].to_i
112
+ self.number_of_failures_excluding_retries += thing[:number_of_failures_excluding_retries].to_i
113
+ self.number_of_retries += thing[:number_of_retries].to_i
114
+ end
115
+ end
116
+
117
+ # Returns the JUnit report as String
118
+ def to_junit
119
+ JunitGenerator.new(self.data).generate
120
+ end
121
+
122
+ # @return [Bool] were all tests successful? Is false if at least one test failed
123
+ def tests_successful?
124
+ self.data.collect { |a| a[:number_of_failures] }.all?(&:zero?)
125
+ end
126
+
127
+ private
128
+
129
+ def ensure_file_valid!
130
+ format_version = self.raw_json["FormatVersion"]
131
+ supported_versions = ["1.1", "1.2"]
132
+ UI.user_error!("Format version '#{format_version}' is not supported, must be #{supported_versions.join(', ')}") unless supported_versions.include?(format_version)
133
+ end
134
+
135
+ # Converts the raw plist test structure into something that's easier to enumerate
136
+ def unfold_tests(data)
137
+ # `data` looks like this
138
+ # => [{"Subtests"=>
139
+ # [{"Subtests"=>
140
+ # [{"Subtests"=>
141
+ # [{"Duration"=>0.4,
142
+ # "TestIdentifier"=>"Unit/testExample()",
143
+ # "TestName"=>"testExample()",
144
+ # "TestObjectClass"=>"IDESchemeActionTestSummary",
145
+ # "TestStatus"=>"Success",
146
+ # "TestSummaryGUID"=>"4A24BFED-03E6-4FBE-BC5E-2D80023C06B4"},
147
+ # {"FailureSummaries"=>
148
+ # [{"FileName"=>"/Users/krausefx/Developer/themoji/Unit/Unit.swift",
149
+ # "LineNumber"=>34,
150
+ # "Message"=>"XCTAssertTrue failed - ",
151
+ # "PerformanceFailure"=>false}],
152
+ # "TestIdentifier"=>"Unit/testExample2()",
153
+
154
+ tests = []
155
+ data.each do |current_hash|
156
+ if current_hash["Subtests"]
157
+ tests += unfold_tests(current_hash["Subtests"])
158
+ end
159
+ if current_hash["TestStatus"]
160
+ tests << current_hash
161
+ end
162
+ end
163
+ return tests
164
+ end
165
+
166
+ # Returns the test group and test name from the passed summary and test
167
+ # Pass xcpretty_naming = true to get the test naming aligned with xcpretty
168
+ def test_group_and_name(testable_summary, test, xcpretty_naming)
169
+ if xcpretty_naming
170
+ group = testable_summary["TargetName"] + "." + test["TestIdentifier"].split("/")[0..-2].join(".")
171
+ name = test["TestName"][0..-3]
172
+ else
173
+ group = test["TestIdentifier"].split("/")[0..-2].join(".")
174
+ name = test["TestName"]
175
+ end
176
+ return group, name
177
+ end
178
+
179
+ def execute_cmd(cmd)
180
+ output = `#{cmd}`
181
+ raise "Failed to execute - #{cmd}" unless $?.success?
182
+ return output
183
+ end
184
+
185
+ def parse_xcresult(path)
186
+ require 'shellwords'
187
+ path = Shellwords.escape(path)
188
+
189
+ # Executes xcresulttool to get JSON format of the result bundle object
190
+ result_bundle_object_raw = execute_cmd("xcrun xcresulttool get --format json --path #{path}")
191
+ result_bundle_object = JSON.parse(result_bundle_object_raw)
192
+
193
+ # Parses JSON into ActionsInvocationRecord to find a list of all ids for ActionTestPlanRunSummaries
194
+ actions_invocation_record = Trainer::XCResult::ActionsInvocationRecord.new(result_bundle_object)
195
+ test_refs = actions_invocation_record.actions.map do |action|
196
+ action.action_result.tests_ref
197
+ end.compact
198
+ ids = test_refs.map(&:id)
199
+
200
+ # Maps ids into ActionTestPlanRunSummaries by executing xcresulttool to get JSON
201
+ # containing specific information for each test summary,
202
+ summaries = ids.map do |id|
203
+ raw = execute_cmd("xcrun xcresulttool get --format json --path #{path} --id #{id}")
204
+ json = JSON.parse(raw)
205
+ Trainer::XCResult::ActionTestPlanRunSummaries.new(json)
206
+ end
207
+
208
+ # Converts the ActionTestPlanRunSummaries to data for junit generator
209
+ failures = actions_invocation_record.issues.test_failure_summaries || []
210
+ summaries_to_data(summaries, failures)
211
+ end
212
+
213
+ def summaries_to_data(summaries, failures)
214
+ # Gets flat list of all ActionTestableSummary
215
+ all_summaries = summaries.map(&:summaries).flatten
216
+ testable_summaries = all_summaries.map(&:testable_summaries).flatten
217
+
218
+ # Maps ActionTestableSummary to rows for junit generator
219
+ rows = testable_summaries.map do |testable_summary|
220
+ all_tests = testable_summary.all_tests.flatten
221
+
222
+ # Used by store number of passes and failures by identifier
223
+ # This is used when Xcode 13 (and up) retries tests
224
+ # The identifier is duplicated until test succeeds or max count is reachd
225
+ tests_by_identifier = {}
226
+
227
+ test_rows = all_tests.map do |test|
228
+ identifier = "#{test.parent.name}.#{test.name}"
229
+ test_row = {
230
+ identifier: identifier,
231
+ name: test.name,
232
+ duration: test.duration,
233
+ status: test.test_status,
234
+ test_group: test.parent.name,
235
+
236
+ # These don't map to anything but keeping empty strings
237
+ guid: ""
238
+ }
239
+
240
+ info = tests_by_identifier[identifier] || {}
241
+ info[:failure_count] ||= 0
242
+ info[:success_count] ||= 0
243
+
244
+ retry_count = info[:retry_count]
245
+ if retry_count.nil?
246
+ retry_count = 0
247
+ else
248
+ retry_count += 1
249
+ end
250
+ info[:retry_count] = retry_count
251
+
252
+ # Set failure message if failure found
253
+ failure = test.find_failure(failures)
254
+ if failure
255
+ test_row[:failures] = [{
256
+ file_name: "",
257
+ line_number: 0,
258
+ message: "",
259
+ performance_failure: {},
260
+ failure_message: failure.failure_message
261
+ }]
262
+
263
+ info[:failure_count] += 1
264
+ else
265
+ info[:success_count] = 1
266
+ end
267
+
268
+ tests_by_identifier[identifier] = info
269
+
270
+ test_row
271
+ end
272
+
273
+ row = {
274
+ project_path: testable_summary.project_relative_path,
275
+ target_name: testable_summary.target_name,
276
+ test_name: testable_summary.name,
277
+ duration: all_tests.map(&:duration).inject(:+),
278
+ tests: test_rows
279
+ }
280
+
281
+ row[:number_of_tests] = row[:tests].count
282
+ row[:number_of_failures] = row[:tests].find_all { |a| (a[:failures] || []).count > 0 }.count
283
+
284
+ # Used for seeing if any tests continued to fail after all of the Xcode 13 (and up) retries have finished
285
+ unique_tests = tests_by_identifier.values || []
286
+ row[:number_of_tests_excluding_retries] = unique_tests.count
287
+ row[:number_of_failures_excluding_retries] = unique_tests.find_all { |a| a[:success_count] == 0 }.count
288
+ row[:number_of_retries] = unique_tests.map { |a| a[:retry_count] }.inject(:+)
289
+
290
+ row
291
+ end
292
+
293
+ self.data = rows
294
+ end
295
+
296
+ # Convert the Hashes and Arrays in something more useful
297
+ def parse_content(xcpretty_naming)
298
+ self.data = self.raw_json["TestableSummaries"].collect do |testable_summary|
299
+ summary_row = {
300
+ project_path: testable_summary["ProjectPath"],
301
+ target_name: testable_summary["TargetName"],
302
+ test_name: testable_summary["TestName"],
303
+ duration: testable_summary["Tests"].map { |current_test| current_test["Duration"] }.inject(:+),
304
+ tests: unfold_tests(testable_summary["Tests"]).collect do |current_test|
305
+ test_group, test_name = test_group_and_name(testable_summary, current_test, xcpretty_naming)
306
+ current_row = {
307
+ identifier: current_test["TestIdentifier"],
308
+ test_group: test_group,
309
+ name: test_name,
310
+ object_class: current_test["TestObjectClass"],
311
+ status: current_test["TestStatus"],
312
+ guid: current_test["TestSummaryGUID"],
313
+ duration: current_test["Duration"]
314
+ }
315
+ if current_test["FailureSummaries"]
316
+ current_row[:failures] = current_test["FailureSummaries"].collect do |current_failure|
317
+ {
318
+ file_name: current_failure['FileName'],
319
+ line_number: current_failure['LineNumber'],
320
+ message: current_failure['Message'],
321
+ performance_failure: current_failure['PerformanceFailure'],
322
+ failure_message: "#{current_failure['Message']} (#{current_failure['FileName']}:#{current_failure['LineNumber']})"
323
+ }
324
+ end
325
+ end
326
+ current_row
327
+ end
328
+ }
329
+ summary_row[:number_of_tests] = summary_row[:tests].count
330
+ summary_row[:number_of_failures] = summary_row[:tests].find_all { |a| (a[:failures] || []).count > 0 }.count
331
+ summary_row
332
+ end
333
+ end
334
+ end
335
+ end