fastlane 2.196.0 → 2.212.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (211) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +88 -81
  4. data/cert/lib/cert/runner.rb +19 -8
  5. data/deliver/lib/assets/ScreenshotsHelp +29 -6
  6. data/deliver/lib/deliver/app_screenshot.rb +30 -4
  7. data/deliver/lib/deliver/app_screenshot_iterator.rb +1 -1
  8. data/deliver/lib/deliver/options.rb +6 -2
  9. data/deliver/lib/deliver/runner.rb +88 -24
  10. data/deliver/lib/deliver/submit_for_review.rb +25 -3
  11. data/deliver/lib/deliver/upload_price_tier.rb +3 -1
  12. data/deliver/lib/deliver/upload_screenshots.rb +2 -2
  13. data/fastlane/lib/assets/AppfileTemplate +1 -1
  14. data/fastlane/lib/assets/AppfileTemplate.swift +1 -1
  15. data/fastlane/lib/fastlane/actions/badge.rb +1 -1
  16. data/fastlane/lib/fastlane/actions/changelog_from_git_commits.rb +1 -1
  17. data/fastlane/lib/fastlane/actions/danger.rb +14 -0
  18. data/fastlane/lib/fastlane/actions/docs/build_app.md +5 -5
  19. data/fastlane/lib/fastlane/actions/docs/capture_android_screenshots.md +19 -2
  20. data/fastlane/lib/fastlane/actions/docs/frame_screenshots.md +1 -1
  21. data/fastlane/lib/fastlane/actions/docs/run_tests.md +1 -1
  22. data/fastlane/lib/fastlane/actions/docs/upload_to_app_store.md.erb +1 -1
  23. data/fastlane/lib/fastlane/actions/download_dsyms.rb +62 -46
  24. data/fastlane/lib/fastlane/actions/ensure_git_status_clean.rb +15 -4
  25. data/fastlane/lib/fastlane/actions/ensure_xcode_version.rb +1 -1
  26. data/fastlane/lib/fastlane/actions/get_push_certificate.rb +1 -1
  27. data/fastlane/lib/fastlane/actions/get_version_number.rb +8 -3
  28. data/fastlane/lib/fastlane/actions/git_commit.rb +4 -6
  29. data/fastlane/lib/fastlane/actions/import_certificate.rb +1 -1
  30. data/fastlane/lib/fastlane/actions/notarize.rb +29 -11
  31. data/fastlane/lib/fastlane/actions/pod_lib_lint.rb +1 -1
  32. data/fastlane/lib/fastlane/actions/pod_push.rb +19 -1
  33. data/fastlane/lib/fastlane/actions/read_podspec.rb +1 -1
  34. data/fastlane/lib/fastlane/actions/run_tests.rb +19 -9
  35. data/fastlane/lib/fastlane/actions/set_github_release.rb +11 -5
  36. data/fastlane/lib/fastlane/actions/setup_ci.rb +13 -4
  37. data/fastlane/lib/fastlane/actions/trainer.rb +49 -0
  38. data/fastlane/lib/fastlane/actions/update_code_signing_settings.rb +31 -4
  39. data/fastlane/lib/fastlane/actions/update_info_plist.rb +1 -1
  40. data/fastlane/lib/fastlane/actions/update_project_provisioning.rb +10 -1
  41. data/fastlane/lib/fastlane/actions/upload_symbols_to_sentry.rb +1 -1
  42. data/fastlane/lib/fastlane/actions/verify_build.rb +1 -1
  43. data/fastlane/lib/fastlane/actions/xcode_install.rb +5 -1
  44. data/fastlane/lib/fastlane/actions/xcode_select.rb +1 -1
  45. data/fastlane/lib/fastlane/actions/xcodebuild.rb +8 -2
  46. data/fastlane/lib/fastlane/actions/xcodes.rb +152 -0
  47. data/fastlane/lib/fastlane/actions/xcov.rb +5 -0
  48. data/fastlane/lib/fastlane/actions/xcversion.rb +17 -7
  49. data/fastlane/lib/fastlane/cli_tools_distributor.rb +5 -0
  50. data/fastlane/lib/fastlane/commands_generator.rb +2 -1
  51. data/fastlane/lib/fastlane/documentation/docs_generator.rb +17 -12
  52. data/fastlane/lib/fastlane/fast_file.rb +18 -5
  53. data/fastlane/lib/fastlane/features.rb +3 -0
  54. data/fastlane/lib/fastlane/helper/xcodebuild_formatter_helper.rb +9 -0
  55. data/fastlane/lib/fastlane/helper/xcodes_helper.rb +28 -0
  56. data/fastlane/lib/fastlane/helper/xcversion_helper.rb +0 -9
  57. data/fastlane/lib/fastlane/lane_manager.rb +1 -1
  58. data/fastlane/lib/fastlane/plugins/template/%gem_name%.gemspec.erb +1 -1
  59. data/fastlane/lib/fastlane/plugins/template/.rubocop.yml +5 -1
  60. data/fastlane/lib/fastlane/setup/setup_ios.rb +1 -1
  61. data/fastlane/lib/fastlane/swift_fastlane_api_generator.rb +1 -1
  62. data/fastlane/lib/fastlane/swift_lane_manager.rb +11 -3
  63. data/fastlane/lib/fastlane/swift_runner_upgrader.rb +54 -1
  64. data/fastlane/lib/fastlane/tools.rb +18 -1
  65. data/fastlane/lib/fastlane/version.rb +1 -1
  66. data/fastlane/swift/Actions.swift +1 -1
  67. data/fastlane/swift/Appfile.swift +2 -2
  68. data/fastlane/swift/ArgumentProcessor.swift +1 -1
  69. data/fastlane/swift/Atomic.swift +150 -0
  70. data/fastlane/swift/ControlCommand.swift +1 -1
  71. data/fastlane/swift/Deliverfile.swift +2 -2
  72. data/fastlane/swift/DeliverfileProtocol.swift +8 -4
  73. data/fastlane/swift/Fastlane.swift +570 -239
  74. data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/project.pbxproj +30 -20
  75. data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/xcshareddata/xcschemes/FastlaneRunner.xcscheme +1 -1
  76. data/fastlane/swift/Gymfile.swift +2 -2
  77. data/fastlane/swift/GymfileProtocol.swift +20 -8
  78. data/fastlane/swift/LaneFileProtocol.swift +2 -2
  79. data/fastlane/swift/MainProcess.swift +3 -3
  80. data/fastlane/swift/Matchfile.swift +2 -2
  81. data/fastlane/swift/MatchfileProtocol.swift +21 -5
  82. data/fastlane/swift/OptionalConfigValue.swift +1 -1
  83. data/fastlane/swift/Plugins.swift +1 -1
  84. data/fastlane/swift/Precheckfile.swift +2 -2
  85. data/fastlane/swift/PrecheckfileProtocol.swift +3 -3
  86. data/fastlane/swift/RubyCommand.swift +1 -1
  87. data/fastlane/swift/RubyCommandable.swift +1 -1
  88. data/fastlane/swift/Runner.swift +13 -9
  89. data/fastlane/swift/RunnerArgument.swift +1 -1
  90. data/fastlane/swift/Scanfile.swift +2 -2
  91. data/fastlane/swift/ScanfileProtocol.swift +31 -11
  92. data/fastlane/swift/Screengrabfile.swift +2 -2
  93. data/fastlane/swift/ScreengrabfileProtocol.swift +3 -3
  94. data/fastlane/swift/Snapshotfile.swift +2 -2
  95. data/fastlane/swift/SnapshotfileProtocol.swift +12 -8
  96. data/fastlane/swift/SocketClient.swift +9 -5
  97. data/fastlane/swift/SocketClientDelegateProtocol.swift +2 -2
  98. data/fastlane/swift/SocketResponse.swift +1 -1
  99. data/fastlane/swift/formatting/Brewfile.lock.json +46 -23
  100. data/fastlane/swift/main.swift +1 -1
  101. data/fastlane/swift/upgrade_manifest.json +1 -1
  102. data/fastlane_core/README.md +1 -0
  103. data/fastlane_core/lib/fastlane_core/cert_checker.rb +79 -17
  104. data/fastlane_core/lib/fastlane_core/device_manager.rb +5 -1
  105. data/fastlane_core/lib/fastlane_core/ipa_file_analyser.rb +10 -5
  106. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +409 -26
  107. data/fastlane_core/lib/fastlane_core/keychain_importer.rb +1 -0
  108. data/fastlane_core/lib/fastlane_core/project.rb +19 -2
  109. data/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb +7 -0
  110. data/fastlane_core/lib/fastlane_core/ui/implementations/shell.rb +4 -2
  111. data/frameit/lib/frameit/device.rb +1 -1
  112. data/frameit/lib/frameit/device_types.rb +9 -0
  113. data/frameit/lib/frameit/editor.rb +16 -18
  114. data/frameit/lib/frameit/frame_downloader.rb +1 -1
  115. data/frameit/lib/frameit/trim_box.rb +6 -0
  116. data/gym/lib/gym/generators/build_command_generator.rb +70 -23
  117. data/gym/lib/gym/options.rb +30 -5
  118. data/match/lib/match/change_password.rb +2 -0
  119. data/match/lib/match/commands_generator.rb +2 -1
  120. data/match/lib/match/encryption/openssl.rb +1 -1
  121. data/match/lib/match/encryption.rb +3 -0
  122. data/match/lib/match/generator.rb +1 -0
  123. data/match/lib/match/importer.rb +10 -1
  124. data/match/lib/match/migrate.rb +4 -3
  125. data/match/lib/match/module.rb +54 -2
  126. data/match/lib/match/nuke.rb +114 -47
  127. data/match/lib/match/options.rb +22 -1
  128. data/match/lib/match/runner.rb +25 -6
  129. data/match/lib/match/setup.rb +1 -1
  130. data/match/lib/match/spaceship_ensure.rb +5 -2
  131. data/match/lib/match/storage/gitlab/client.rb +102 -0
  132. data/match/lib/match/storage/gitlab/secure_file.rb +65 -0
  133. data/match/lib/match/storage/gitlab_secure_files.rb +182 -0
  134. data/match/lib/match/storage/google_cloud_storage.rb +7 -6
  135. data/match/lib/match/storage/s3_storage.rb +3 -3
  136. data/match/lib/match/storage.rb +4 -0
  137. data/match/lib/match/table_printer.rb +2 -1
  138. data/match/lib/match/utils.rb +15 -2
  139. data/pem/lib/pem/manager.rb +30 -7
  140. data/pem/lib/pem/options.rb +9 -0
  141. data/pilot/lib/pilot/build_manager.rb +34 -14
  142. data/pilot/lib/pilot/options.rb +6 -1
  143. data/scan/lib/scan/detect_values.rb +6 -0
  144. data/scan/lib/scan/error_handler.rb +9 -0
  145. data/scan/lib/scan/options.rb +49 -9
  146. data/scan/lib/scan/runner.rb +171 -25
  147. data/scan/lib/scan/test_command_generator.rb +65 -5
  148. data/sigh/lib/sigh/download_all.rb +14 -2
  149. data/sigh/lib/sigh/module.rb +3 -1
  150. data/sigh/lib/sigh/options.rb +5 -0
  151. data/sigh/lib/sigh/runner.rb +12 -2
  152. data/snapshot/lib/assets/SnapshotHelper.swift +3 -3
  153. data/snapshot/lib/snapshot/latest_os_version.rb +2 -5
  154. data/snapshot/lib/snapshot/options.rb +24 -8
  155. data/snapshot/lib/snapshot/reports_generator.rb +1 -0
  156. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +10 -3
  157. data/snapshot/lib/snapshot/test_command_generator.rb +37 -2
  158. data/spaceship/lib/spaceship/client.rb +71 -40
  159. data/spaceship/lib/spaceship/commands_generator.rb +1 -1
  160. data/spaceship/lib/spaceship/connect_api/api_client.rb +10 -5
  161. data/spaceship/lib/spaceship/connect_api/models/actor.rb +26 -0
  162. data/spaceship/lib/spaceship/connect_api/models/app.rb +52 -6
  163. data/spaceship/lib/spaceship/connect_api/models/app_info.rb +1 -0
  164. data/spaceship/lib/spaceship/connect_api/models/app_info_localization.rb +5 -0
  165. data/spaceship/lib/spaceship/connect_api/models/app_screenshot_set.rb +7 -0
  166. data/spaceship/lib/spaceship/connect_api/models/app_store_version.rb +1 -1
  167. data/spaceship/lib/spaceship/connect_api/models/app_store_version_localization.rb +27 -10
  168. data/spaceship/lib/spaceship/connect_api/models/build.rb +4 -2
  169. data/spaceship/lib/spaceship/connect_api/models/build_bundle.rb +68 -0
  170. data/spaceship/lib/spaceship/connect_api/models/build_bundle_file_sizes.rb +34 -0
  171. data/spaceship/lib/spaceship/connect_api/models/build_delivery.rb +2 -1
  172. data/spaceship/lib/spaceship/connect_api/models/certificate.rb +4 -0
  173. data/spaceship/lib/spaceship/connect_api/models/device.rb +47 -4
  174. data/spaceship/lib/spaceship/connect_api/models/profile.rb +4 -0
  175. data/spaceship/lib/spaceship/connect_api/models/resolution_center_message.rb +29 -0
  176. data/spaceship/lib/spaceship/connect_api/models/resolution_center_thread.rb +67 -0
  177. data/spaceship/lib/spaceship/connect_api/models/review_rejection.rb +19 -0
  178. data/spaceship/lib/spaceship/connect_api/models/review_submission.rb +86 -0
  179. data/spaceship/lib/spaceship/connect_api/models/review_submission_item.rb +40 -0
  180. data/spaceship/lib/spaceship/connect_api/models/user.rb +5 -0
  181. data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +19 -0
  182. data/spaceship/lib/spaceship/connect_api/response.rb +23 -6
  183. data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +33 -2
  184. data/spaceship/lib/spaceship/connect_api/token.rb +5 -2
  185. data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +124 -8
  186. data/spaceship/lib/spaceship/connect_api.rb +9 -0
  187. data/spaceship/lib/spaceship/errors.rb +34 -0
  188. data/spaceship/lib/spaceship/globals.rb +9 -0
  189. data/spaceship/lib/spaceship/hashcash.rb +52 -0
  190. data/spaceship/lib/spaceship/portal/certificate.rb +4 -3
  191. data/spaceship/lib/spaceship/spaceauth_runner.rb +1 -1
  192. data/spaceship/lib/spaceship/tunes/app_ratings.rb +6 -6
  193. data/spaceship/lib/spaceship/tunes/iap_families.rb +1 -1
  194. data/spaceship/lib/spaceship/tunes/tunes.rb +0 -1
  195. data/spaceship/lib/spaceship/tunes/tunes_client.rb +79 -21
  196. data/spaceship/lib/spaceship/two_step_or_factor_client.rb +11 -3
  197. data/spaceship/lib/spaceship.rb +1 -0
  198. data/supply/lib/supply/client.rb +2 -7
  199. data/supply/lib/supply/options.rb +8 -0
  200. data/supply/lib/supply/uploader.rb +6 -2
  201. data/trainer/lib/assets/junit.xml.erb +28 -0
  202. data/trainer/lib/trainer/commands_generator.rb +51 -0
  203. data/trainer/lib/trainer/junit_generator.rb +31 -0
  204. data/trainer/lib/trainer/module.rb +10 -0
  205. data/trainer/lib/trainer/options.rb +66 -0
  206. data/trainer/lib/trainer/test_parser.rb +398 -0
  207. data/trainer/lib/trainer/xcresult.rb +403 -0
  208. data/trainer/lib/trainer.rb +7 -0
  209. metadata +49 -24
  210. data/spaceship/lib/spaceship/connect_api/testflight/.testflight.rb.swp +0 -0
  211. data/spaceship/lib/spaceship/tunes/user_detail.rb +0 -15
@@ -0,0 +1,398 @@
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
+ attr_accessor :number_of_skipped
23
+
24
+ # Returns a hash with the path being the key, and the value
25
+ # defining if the tests were successful
26
+ def self.auto_convert(config)
27
+ unless config[:silent]
28
+ FastlaneCore::PrintTable.print_values(config: config,
29
+ title: "Summary for trainer #{Fastlane::VERSION}")
30
+ end
31
+
32
+ containing_dir = config[:path]
33
+ # Xcode < 10
34
+ files = Dir["#{containing_dir}/**/Logs/Test/*TestSummaries.plist"]
35
+ files += Dir["#{containing_dir}/Test/*TestSummaries.plist"]
36
+ files += Dir["#{containing_dir}/*TestSummaries.plist"]
37
+ # Xcode 10
38
+ files += Dir["#{containing_dir}/**/Logs/Test/*.xcresult/TestSummaries.plist"]
39
+ files += Dir["#{containing_dir}/Test/*.xcresult/TestSummaries.plist"]
40
+ files += Dir["#{containing_dir}/*.xcresult/TestSummaries.plist"]
41
+ files += Dir[containing_dir] if containing_dir.end_with?(".plist") # if it's the exact path to a plist file
42
+ # Xcode 11
43
+ files += Dir["#{containing_dir}/**/Logs/Test/*.xcresult"]
44
+ files += Dir["#{containing_dir}/Test/*.xcresult"]
45
+ files += Dir["#{containing_dir}/*.xcresult"]
46
+ files << containing_dir if File.extname(containing_dir) == ".xcresult"
47
+
48
+ if files.empty?
49
+ UI.user_error!("No test result files found in directory '#{containing_dir}', make sure the file name ends with 'TestSummaries.plist' or '.xcresult'")
50
+ end
51
+
52
+ return_hash = {}
53
+ files.each do |path|
54
+ extension = config[:extension]
55
+ output_filename = config[:output_filename]
56
+
57
+ should_write_file = !extension.nil? || !output_filename.nil?
58
+
59
+ if should_write_file
60
+ if config[:output_directory]
61
+ FileUtils.mkdir_p(config[:output_directory])
62
+ # Remove .xcresult or .plist extension
63
+ # Use custom file name ONLY if one file otherwise issues
64
+ if files.size == 1 && output_filename
65
+ filename = output_filename
66
+ elsif path.end_with?(".xcresult")
67
+ filename ||= File.basename(path).gsub(".xcresult", extension)
68
+ else
69
+ filename ||= File.basename(path).gsub(".plist", extension)
70
+ end
71
+ to_path = File.join(config[:output_directory], filename)
72
+ else
73
+ # Remove .xcresult or .plist extension
74
+ if path.end_with?(".xcresult")
75
+ to_path = path.gsub(".xcresult", extension)
76
+ else
77
+ to_path = path.gsub(".plist", extension)
78
+ end
79
+ end
80
+ end
81
+
82
+ tp = Trainer::TestParser.new(path, config)
83
+ File.write(to_path, tp.to_junit) if should_write_file
84
+ UI.success("Successfully generated '#{to_path}'") if should_write_file && !config[:silent]
85
+
86
+ return_hash[path] = {
87
+ to_path: to_path,
88
+ successful: tp.tests_successful?,
89
+ number_of_tests: tp.number_of_tests,
90
+ number_of_failures: tp.number_of_failures,
91
+ number_of_tests_excluding_retries: tp.number_of_tests_excluding_retries,
92
+ number_of_failures_excluding_retries: tp.number_of_failures_excluding_retries,
93
+ number_of_retries: tp.number_of_retries,
94
+ number_of_skipped: tp.number_of_skipped
95
+ }
96
+ end
97
+ return_hash
98
+ end
99
+
100
+ def initialize(path, config = {})
101
+ path = File.expand_path(path)
102
+ UI.user_error!("File not found at path '#{path}'") unless File.exist?(path)
103
+
104
+ if File.directory?(path) && path.end_with?(".xcresult")
105
+ parse_xcresult(path, output_remove_retry_attempts: config[:output_remove_retry_attempts])
106
+ else
107
+ self.file_content = File.read(path)
108
+ self.raw_json = Plist.parse_xml(self.file_content)
109
+
110
+ return if self.raw_json["FormatVersion"].to_s.length.zero? # maybe that's a useless plist file
111
+
112
+ ensure_file_valid!
113
+ parse_content(config[:xcpretty_naming])
114
+ end
115
+
116
+ self.number_of_tests = 0
117
+ self.number_of_failures = 0
118
+ self.number_of_tests_excluding_retries = 0
119
+ self.number_of_failures_excluding_retries = 0
120
+ self.number_of_retries = 0
121
+ self.number_of_skipped = 0
122
+ self.data.each do |thing|
123
+ self.number_of_tests += thing[:number_of_tests].to_i
124
+ self.number_of_failures += thing[:number_of_failures].to_i
125
+ self.number_of_tests_excluding_retries += thing[:number_of_tests_excluding_retries].to_i
126
+ self.number_of_failures_excluding_retries += thing[:number_of_failures_excluding_retries].to_i
127
+ self.number_of_retries += thing[:number_of_retries].to_i
128
+ self.number_of_skipped += thing[:number_of_skipped].to_i
129
+ end
130
+ end
131
+
132
+ # Returns the JUnit report as String
133
+ def to_junit
134
+ JunitGenerator.new(self.data).generate
135
+ end
136
+
137
+ # @return [Bool] were all tests successful? Is false if at least one test failed
138
+ def tests_successful?
139
+ self.data.collect { |a| a[:number_of_failures_excluding_retries] }.all?(&:zero?)
140
+ end
141
+
142
+ private
143
+
144
+ def ensure_file_valid!
145
+ format_version = self.raw_json["FormatVersion"]
146
+ supported_versions = ["1.1", "1.2"]
147
+ UI.user_error!("Format version '#{format_version}' is not supported, must be #{supported_versions.join(', ')}") unless supported_versions.include?(format_version)
148
+ end
149
+
150
+ # Converts the raw plist test structure into something that's easier to enumerate
151
+ def unfold_tests(data)
152
+ # `data` looks like this
153
+ # => [{"Subtests"=>
154
+ # [{"Subtests"=>
155
+ # [{"Subtests"=>
156
+ # [{"Duration"=>0.4,
157
+ # "TestIdentifier"=>"Unit/testExample()",
158
+ # "TestName"=>"testExample()",
159
+ # "TestObjectClass"=>"IDESchemeActionTestSummary",
160
+ # "TestStatus"=>"Success",
161
+ # "TestSummaryGUID"=>"4A24BFED-03E6-4FBE-BC5E-2D80023C06B4"},
162
+ # {"FailureSummaries"=>
163
+ # [{"FileName"=>"/Users/krausefx/Developer/themoji/Unit/Unit.swift",
164
+ # "LineNumber"=>34,
165
+ # "Message"=>"XCTAssertTrue failed - ",
166
+ # "PerformanceFailure"=>false}],
167
+ # "TestIdentifier"=>"Unit/testExample2()",
168
+
169
+ tests = []
170
+ data.each do |current_hash|
171
+ if current_hash["Subtests"]
172
+ tests += unfold_tests(current_hash["Subtests"])
173
+ end
174
+ if current_hash["TestStatus"]
175
+ tests << current_hash
176
+ end
177
+ end
178
+ return tests
179
+ end
180
+
181
+ # Returns the test group and test name from the passed summary and test
182
+ # Pass xcpretty_naming = true to get the test naming aligned with xcpretty
183
+ def test_group_and_name(testable_summary, test, xcpretty_naming)
184
+ if xcpretty_naming
185
+ group = testable_summary["TargetName"] + "." + test["TestIdentifier"].split("/")[0..-2].join(".")
186
+ name = test["TestName"][0..-3]
187
+ else
188
+ group = test["TestIdentifier"].split("/")[0..-2].join(".")
189
+ name = test["TestName"]
190
+ end
191
+ return group, name
192
+ end
193
+
194
+ def execute_cmd(cmd)
195
+ output = `#{cmd}`
196
+ raise "Failed to execute - #{cmd}" unless $?.success?
197
+ return output
198
+ end
199
+
200
+ def parse_xcresult(path, output_remove_retry_attempts: false)
201
+ require 'shellwords'
202
+ path = Shellwords.escape(path)
203
+
204
+ # Executes xcresulttool to get JSON format of the result bundle object
205
+ result_bundle_object_raw = execute_cmd("xcrun xcresulttool get --format json --path #{path}")
206
+ result_bundle_object = JSON.parse(result_bundle_object_raw)
207
+
208
+ # Parses JSON into ActionsInvocationRecord to find a list of all ids for ActionTestPlanRunSummaries
209
+ actions_invocation_record = Trainer::XCResult::ActionsInvocationRecord.new(result_bundle_object)
210
+ test_refs = actions_invocation_record.actions.map do |action|
211
+ action.action_result.tests_ref
212
+ end.compact
213
+ ids = test_refs.map(&:id)
214
+
215
+ # Maps ids into ActionTestPlanRunSummaries by executing xcresulttool to get JSON
216
+ # containing specific information for each test summary,
217
+ summaries = ids.map do |id|
218
+ raw = execute_cmd("xcrun xcresulttool get --format json --path #{path} --id #{id}")
219
+ json = JSON.parse(raw)
220
+ Trainer::XCResult::ActionTestPlanRunSummaries.new(json)
221
+ end
222
+
223
+ # Converts the ActionTestPlanRunSummaries to data for junit generator
224
+ failures = actions_invocation_record.issues.test_failure_summaries || []
225
+ summaries_to_data(summaries, failures, output_remove_retry_attempts: output_remove_retry_attempts)
226
+ end
227
+
228
+ def summaries_to_data(summaries, failures, output_remove_retry_attempts: false)
229
+ # Gets flat list of all ActionTestableSummary
230
+ all_summaries = summaries.map(&:summaries).flatten
231
+ testable_summaries = all_summaries.map(&:testable_summaries).flatten
232
+
233
+ summaries_to_names = test_summaries_to_configuration_names(all_summaries)
234
+
235
+ # Maps ActionTestableSummary to rows for junit generator
236
+ rows = testable_summaries.map do |testable_summary|
237
+ all_tests = testable_summary.all_tests.flatten
238
+
239
+ # Used by store number of passes and failures by identifier
240
+ # This is used when Xcode 13 (and up) retries tests
241
+ # The identifier is duplicated until test succeeds or max count is reachd
242
+ tests_by_identifier = {}
243
+
244
+ test_rows = all_tests.map do |test|
245
+ identifier = "#{test.parent.name}.#{test.name}"
246
+ test_row = {
247
+ identifier: identifier,
248
+ name: test.name,
249
+ duration: test.duration,
250
+ status: test.test_status,
251
+ test_group: test.parent.name,
252
+
253
+ # These don't map to anything but keeping empty strings
254
+ guid: ""
255
+ }
256
+
257
+ info = tests_by_identifier[identifier] || {}
258
+ info[:failure_count] ||= 0
259
+ info[:skip_count] ||= 0
260
+ info[:success_count] ||= 0
261
+
262
+ retry_count = info[:retry_count]
263
+ if retry_count.nil?
264
+ retry_count = 0
265
+ else
266
+ retry_count += 1
267
+ end
268
+ info[:retry_count] = retry_count
269
+
270
+ # Set failure message if failure found
271
+ failure = test.find_failure(failures)
272
+ if failure
273
+ test_row[:failures] = [{
274
+ file_name: "",
275
+ line_number: 0,
276
+ message: "",
277
+ performance_failure: {},
278
+ failure_message: failure.failure_message
279
+ }]
280
+
281
+ info[:failure_count] += 1
282
+ elsif test.test_status == "Skipped"
283
+ test_row[:skipped] = true
284
+ info[:skip_count] += 1
285
+ else
286
+ info[:success_count] = 1
287
+ end
288
+
289
+ tests_by_identifier[identifier] = info
290
+
291
+ test_row
292
+ end
293
+
294
+ # Remove retry attempts from the count and test rows
295
+ if output_remove_retry_attempts
296
+ test_rows = test_rows.reject do |test_row|
297
+ remove = false
298
+
299
+ identifier = test_row[:identifier]
300
+ info = tests_by_identifier[identifier]
301
+
302
+ # Remove if this row is a retry and is a failure
303
+ if info[:retry_count] > 0
304
+ remove = !(test_row[:failures] || []).empty?
305
+ end
306
+
307
+ # Remove all failure and retry count if test did eventually pass
308
+ if remove
309
+ info[:failure_count] -= 1
310
+ info[:retry_count] -= 1
311
+ tests_by_identifier[identifier] = info
312
+ end
313
+
314
+ remove
315
+ end
316
+ end
317
+
318
+ row = {
319
+ project_path: testable_summary.project_relative_path,
320
+ target_name: testable_summary.target_name,
321
+ test_name: testable_summary.name,
322
+ configuration_name: summaries_to_names[testable_summary],
323
+ duration: all_tests.map(&:duration).inject(:+),
324
+ tests: test_rows
325
+ }
326
+
327
+ row[:number_of_tests] = row[:tests].count
328
+ row[:number_of_failures] = row[:tests].find_all { |a| (a[:failures] || []).count > 0 }.count
329
+
330
+ # Used for seeing if any tests continued to fail after all of the Xcode 13 (and up) retries have finished
331
+ unique_tests = tests_by_identifier.values || []
332
+ row[:number_of_tests_excluding_retries] = unique_tests.count
333
+ row[:number_of_skipped] = unique_tests.map { |a| a[:skip_count] }.inject(:+)
334
+ row[:number_of_failures_excluding_retries] = unique_tests.find_all { |a| (a[:success_count] + a[:skip_count]) == 0 }.count
335
+ row[:number_of_retries] = unique_tests.map { |a| a[:retry_count] }.inject(:+)
336
+
337
+ row
338
+ end
339
+
340
+ self.data = rows
341
+ end
342
+
343
+ def test_summaries_to_configuration_names(test_summaries)
344
+ summary_to_name = {}
345
+ test_summaries.each do |summary|
346
+ summary.testable_summaries.each do |testable_summary|
347
+ summary_to_name[testable_summary] = summary.name
348
+ end
349
+ end
350
+ summary_to_name
351
+ end
352
+
353
+ # Convert the Hashes and Arrays in something more useful
354
+ def parse_content(xcpretty_naming)
355
+ self.data = self.raw_json["TestableSummaries"].collect do |testable_summary|
356
+ summary_row = {
357
+ project_path: testable_summary["ProjectPath"],
358
+ target_name: testable_summary["TargetName"],
359
+ test_name: testable_summary["TestName"],
360
+ duration: testable_summary["Tests"].map { |current_test| current_test["Duration"] }.inject(:+),
361
+ tests: unfold_tests(testable_summary["Tests"]).collect do |current_test|
362
+ test_group, test_name = test_group_and_name(testable_summary, current_test, xcpretty_naming)
363
+ current_row = {
364
+ identifier: current_test["TestIdentifier"],
365
+ test_group: test_group,
366
+ name: test_name,
367
+ object_class: current_test["TestObjectClass"],
368
+ status: current_test["TestStatus"],
369
+ guid: current_test["TestSummaryGUID"],
370
+ duration: current_test["Duration"]
371
+ }
372
+ if current_test["FailureSummaries"]
373
+ current_row[:failures] = current_test["FailureSummaries"].collect do |current_failure|
374
+ {
375
+ file_name: current_failure['FileName'],
376
+ line_number: current_failure['LineNumber'],
377
+ message: current_failure['Message'],
378
+ performance_failure: current_failure['PerformanceFailure'],
379
+ failure_message: "#{current_failure['Message']} (#{current_failure['FileName']}:#{current_failure['LineNumber']})"
380
+ }
381
+ end
382
+ end
383
+ current_row
384
+ end
385
+ }
386
+ summary_row[:number_of_tests] = summary_row[:tests].count
387
+ summary_row[:number_of_failures] = summary_row[:tests].find_all { |a| (a[:failures] || []).count > 0 }.count
388
+
389
+ # Makes sure that plist support matches data output of xcresult
390
+ summary_row[:number_of_tests_excluding_retries] = summary_row[:number_of_tests]
391
+ summary_row[:number_of_failures_excluding_retries] = summary_row[:number_of_failures]
392
+ summary_row[:number_of_retries] = 0
393
+
394
+ summary_row
395
+ end
396
+ end
397
+ end
398
+ end