fastlane 2.226.0 → 2.227.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +96 -96
- data/cert/lib/cert/options.rb +7 -2
- data/cert/lib/cert/runner.rb +23 -11
- data/deliver/lib/deliver/options.rb +1 -1
- data/fastlane/lib/fastlane/actions/app_store_build_number.rb +1 -1
- data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +1 -1
- data/fastlane/lib/fastlane/actions/latest_testflight_build_number.rb +1 -1
- data/fastlane/lib/fastlane/actions/notarize.rb +4 -0
- data/fastlane/lib/fastlane/actions/onesignal.rb +1 -1
- data/fastlane/lib/fastlane/actions/setup_ci.rb +14 -4
- data/fastlane/lib/fastlane/actions/testfairy.rb +5 -2
- data/fastlane/lib/fastlane/actions/unlock_keychain.rb +6 -1
- data/fastlane/lib/fastlane/version.rb +1 -1
- data/fastlane/swift/Actions.swift +1 -1
- data/fastlane/swift/Appfile.swift +1 -1
- data/fastlane/swift/ArgumentProcessor.swift +1 -1
- data/fastlane/swift/Atomic.swift +1 -1
- data/fastlane/swift/ControlCommand.swift +1 -1
- data/fastlane/swift/Deliverfile.swift +2 -2
- data/fastlane/swift/DeliverfileProtocol.swift +2 -2
- data/fastlane/swift/Fastlane.swift +20 -10
- data/fastlane/swift/Gymfile.swift +2 -2
- data/fastlane/swift/GymfileProtocol.swift +2 -2
- data/fastlane/swift/LaneFileProtocol.swift +1 -1
- data/fastlane/swift/MainProcess.swift +1 -1
- data/fastlane/swift/Matchfile.swift +2 -2
- data/fastlane/swift/MatchfileProtocol.swift +2 -2
- data/fastlane/swift/OptionalConfigValue.swift +1 -1
- data/fastlane/swift/Plugins.swift +1 -1
- data/fastlane/swift/Precheckfile.swift +2 -2
- data/fastlane/swift/PrecheckfileProtocol.swift +2 -2
- data/fastlane/swift/RubyCommand.swift +1 -1
- data/fastlane/swift/RubyCommandable.swift +1 -1
- data/fastlane/swift/Runner.swift +1 -1
- data/fastlane/swift/RunnerArgument.swift +1 -1
- data/fastlane/swift/Scanfile.swift +2 -2
- data/fastlane/swift/ScanfileProtocol.swift +2 -2
- data/fastlane/swift/Screengrabfile.swift +2 -2
- data/fastlane/swift/ScreengrabfileProtocol.swift +2 -2
- data/fastlane/swift/Snapshotfile.swift +2 -2
- data/fastlane/swift/SnapshotfileProtocol.swift +2 -2
- data/fastlane/swift/SocketClient.swift +1 -1
- data/fastlane/swift/SocketClientDelegateProtocol.swift +1 -1
- data/fastlane/swift/SocketResponse.swift +1 -1
- data/fastlane/swift/main.swift +1 -1
- data/fastlane_core/lib/fastlane_core/helper.rb +6 -1
- data/match/lib/assets/READMETemplate.md +2 -2
- data/match/lib/match/generator.rb +2 -2
- data/match/lib/match/runner.rb +1 -1
- data/precheck/lib/precheck/options.rb +1 -1
- data/produce/lib/produce/options.rb +1 -1
- data/spaceship/lib/spaceship/connect_api/models/certificate.rb +1 -0
- data/supply/lib/supply/uploader.rb +19 -12
- data/trainer/lib/trainer/legacy_xcresult.rb +586 -0
- data/trainer/lib/trainer/options.rb +5 -0
- data/trainer/lib/trainer/plist_test_summary_parser.rb +84 -0
- data/trainer/lib/trainer/test_parser.rb +12 -293
- data/trainer/lib/trainer/xcresult/helper.rb +53 -0
- data/trainer/lib/trainer/xcresult/repetition.rb +39 -0
- data/trainer/lib/trainer/xcresult/test_case.rb +221 -0
- data/trainer/lib/trainer/xcresult/test_case_attributes.rb +49 -0
- data/trainer/lib/trainer/xcresult/test_plan.rb +91 -0
- data/trainer/lib/trainer/xcresult/test_suite.rb +134 -0
- data/trainer/lib/trainer/xcresult.rb +31 -388
- data/trainer/lib/trainer.rb +3 -1
- metadata +29 -21
@@ -0,0 +1,49 @@
|
|
1
|
+
module Trainer
|
2
|
+
module XCResult
|
3
|
+
# Mixin for shared attributes between TestCase and Repetition
|
4
|
+
module TestCaseAttributes
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
def passed?
|
10
|
+
@result == 'Passed'
|
11
|
+
end
|
12
|
+
|
13
|
+
def failed?
|
14
|
+
@result == 'Failed'
|
15
|
+
end
|
16
|
+
|
17
|
+
def skipped?
|
18
|
+
@result == 'Skipped'
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
def parse_duration(duration_str)
|
23
|
+
return 0.0 if duration_str.nil?
|
24
|
+
|
25
|
+
# Handle comma-separated duration, and remove 's' suffix
|
26
|
+
duration_str.gsub(',', '.').chomp('s').to_f
|
27
|
+
end
|
28
|
+
|
29
|
+
def extract_failure_messages(node)
|
30
|
+
node['children']
|
31
|
+
&.select { |child| child['nodeType'] == 'Failure Message' }
|
32
|
+
&.map { |msg| msg['name'] } || []
|
33
|
+
end
|
34
|
+
|
35
|
+
def extract_source_references(node)
|
36
|
+
node['children']
|
37
|
+
&.select { |child| child['nodeType'] == 'Source Code Reference' }
|
38
|
+
&.map { |ref| ref['name'] } || []
|
39
|
+
end
|
40
|
+
|
41
|
+
def extract_attachments(node)
|
42
|
+
node['children']
|
43
|
+
&.select { |child| child['nodeType'] == 'Attachment' }
|
44
|
+
&.map { |attachment| attachment['name'] } || []
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require_relative 'test_suite'
|
2
|
+
|
3
|
+
module Trainer
|
4
|
+
module XCResult
|
5
|
+
# Represents a collection of test suites + the configuration, and device used to run them
|
6
|
+
class TestPlan
|
7
|
+
attr_reader :test_suites, :name, :configurations, :devices
|
8
|
+
attr_accessor :output_remove_retry_attempts
|
9
|
+
|
10
|
+
def initialize(test_suites:, configurations: [], devices: [], output_remove_retry_attempts: false)
|
11
|
+
@test_suites = test_suites
|
12
|
+
@configurations = configurations
|
13
|
+
@devices = devices
|
14
|
+
@output_remove_retry_attempts = output_remove_retry_attempts
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.from_json(json:)
|
18
|
+
# Extract configurations and devices
|
19
|
+
configurations = json['testPlanConfigurations'] || []
|
20
|
+
devices = json['devices'] || []
|
21
|
+
|
22
|
+
# Find the test plan node (root of test results)
|
23
|
+
test_plan_node = json['testNodes']&.find { |node| node['nodeType'] == 'Test Plan' }
|
24
|
+
return new(test_suites: []) if test_plan_node.nil?
|
25
|
+
|
26
|
+
# Convert test plan node's children (test bundles) to TestSuite objects
|
27
|
+
test_suites = test_plan_node['children']&.map do |test_bundle|
|
28
|
+
TestSuite.from_json(
|
29
|
+
node: test_bundle
|
30
|
+
)
|
31
|
+
end || []
|
32
|
+
|
33
|
+
new(
|
34
|
+
test_suites: test_suites,
|
35
|
+
configurations: configurations,
|
36
|
+
devices: devices
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Allows iteration over test suites. Used by TestParser to collect test results
|
41
|
+
include Enumerable
|
42
|
+
def each(&block)
|
43
|
+
test_suites.map(&:to_hash).each(&block)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Generates a JUnit-compatible XML representation of the test plan
|
47
|
+
# See https://github.com/testmoapp/junitxml/
|
48
|
+
def to_xml
|
49
|
+
# Create the root testsuites element with calculated summary attributes
|
50
|
+
testsuites = Helper.create_xml_element('testsuites',
|
51
|
+
tests: test_suites.sum(&:test_cases_count).to_s,
|
52
|
+
failures: test_suites.sum(&:failures_count).to_s,
|
53
|
+
skipped: test_suites.sum(&:skipped_count).to_s,
|
54
|
+
time: test_suites.sum(&:duration).to_s)
|
55
|
+
|
56
|
+
# Create <properties> node for configuration and device, to be applied to each suite node
|
57
|
+
properties = Helper.create_xml_element('properties').tap do |node|
|
58
|
+
@configurations.each do |config|
|
59
|
+
config_prop = Helper.create_xml_element('property', name: 'Configuration', value: config['configurationName'])
|
60
|
+
node.add_element(config_prop)
|
61
|
+
end
|
62
|
+
|
63
|
+
@devices.each do |device|
|
64
|
+
device_prop = Helper.create_xml_element('property', name: 'device', value: "#{device.fetch('modelName', 'Unknown Device')} (#{device.fetch('osVersion', 'Unknown OS Version')})")
|
65
|
+
node.add_element(device_prop)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Add each test suite to the root
|
70
|
+
test_suites.each do |suite|
|
71
|
+
suite_node = suite.to_xml(output_remove_retry_attempts: output_remove_retry_attempts)
|
72
|
+
# In JUnit conventions, the <testsuites> root element can't have properties
|
73
|
+
# So we add the <properties> node to each child <testsuite> node instead
|
74
|
+
suite_node.add_element(properties.dup) if properties.elements.any?
|
75
|
+
testsuites.add_element(suite_node)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Convert to XML string with prologue
|
79
|
+
doc = REXML::Document.new
|
80
|
+
doc << REXML::XMLDecl.new('1.0', 'UTF-8')
|
81
|
+
|
82
|
+
doc.add(testsuites)
|
83
|
+
|
84
|
+
formatter = REXML::Formatters::Pretty.new
|
85
|
+
output = String.new
|
86
|
+
formatter.write(doc, output)
|
87
|
+
output
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require_relative 'test_case'
|
2
|
+
|
3
|
+
module Trainer
|
4
|
+
module XCResult
|
5
|
+
# Represents a test suite, including its test cases and sub-suites
|
6
|
+
class TestSuite
|
7
|
+
attr_reader :name
|
8
|
+
attr_reader :identifier
|
9
|
+
attr_reader :type
|
10
|
+
attr_reader :result
|
11
|
+
attr_reader :test_cases
|
12
|
+
attr_reader :sub_suites
|
13
|
+
attr_reader :tags
|
14
|
+
|
15
|
+
def initialize(name:, identifier:, type:, result:, tags: [], test_cases: [], sub_suites: [])
|
16
|
+
@name = name
|
17
|
+
@identifier = identifier
|
18
|
+
@type = type
|
19
|
+
@result = result
|
20
|
+
@tags = tags
|
21
|
+
@test_cases = test_cases
|
22
|
+
@sub_suites = sub_suites
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.from_json(node:)
|
26
|
+
# Create initial TestSuite with basic attributes
|
27
|
+
test_suite = new(
|
28
|
+
name: node['name'],
|
29
|
+
identifier: node['nodeIdentifier'],
|
30
|
+
type: node['nodeType'],
|
31
|
+
result: node['result'],
|
32
|
+
tags: node['tags'] || []
|
33
|
+
)
|
34
|
+
|
35
|
+
# Process children to populate test_cases and sub_suites
|
36
|
+
test_suite.process_children(node['children'] || [])
|
37
|
+
|
38
|
+
test_suite
|
39
|
+
end
|
40
|
+
|
41
|
+
def passed?
|
42
|
+
@result == 'Passed'
|
43
|
+
end
|
44
|
+
|
45
|
+
def failed?
|
46
|
+
@result == 'Failed'
|
47
|
+
end
|
48
|
+
|
49
|
+
def skipped?
|
50
|
+
@result == 'Skipped'
|
51
|
+
end
|
52
|
+
|
53
|
+
def duration
|
54
|
+
@duration ||= @test_cases.sum(&:duration) + @sub_suites.sum(&:duration)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_cases_count
|
58
|
+
@test_cases_count ||= @test_cases.count + @sub_suites.sum(&:test_cases_count)
|
59
|
+
end
|
60
|
+
|
61
|
+
def failures_count
|
62
|
+
@failures_count ||= @test_cases.count(&:failed?) + @sub_suites.sum(&:failures_count)
|
63
|
+
end
|
64
|
+
|
65
|
+
def skipped_count
|
66
|
+
@skipped_count ||= @test_cases.count(&:skipped?) + @sub_suites.sum(&:skipped_count)
|
67
|
+
end
|
68
|
+
|
69
|
+
def total_tests_count
|
70
|
+
@test_cases.sum(&:total_tests_count) +
|
71
|
+
@sub_suites.sum(&:total_tests_count)
|
72
|
+
end
|
73
|
+
|
74
|
+
def total_failures_count
|
75
|
+
@test_cases.sum(&:total_failures_count) +
|
76
|
+
@sub_suites.sum(&:total_failures_count)
|
77
|
+
end
|
78
|
+
|
79
|
+
def total_retries_count
|
80
|
+
@test_cases.sum(&:retries_count) +
|
81
|
+
@sub_suites.sum(&:total_retries_count)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Hash representation used by TestParser to collect test results
|
85
|
+
def to_hash
|
86
|
+
{
|
87
|
+
number_of_tests: total_tests_count,
|
88
|
+
number_of_failures: total_failures_count,
|
89
|
+
number_of_tests_excluding_retries: test_cases_count,
|
90
|
+
number_of_failures_excluding_retries: failures_count,
|
91
|
+
number_of_retries: total_retries_count,
|
92
|
+
number_of_skipped: skipped_count
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
# Generates a JUnit-compatible XML representation of the test suite
|
97
|
+
# See https://github.com/testmoapp/junitxml/
|
98
|
+
def to_xml(output_remove_retry_attempts: false)
|
99
|
+
testsuite = Helper.create_xml_element('testsuite',
|
100
|
+
name: @name,
|
101
|
+
time: duration.to_s,
|
102
|
+
tests: test_cases_count.to_s,
|
103
|
+
failures: failures_count.to_s,
|
104
|
+
skipped: skipped_count.to_s)
|
105
|
+
|
106
|
+
# Add test cases
|
107
|
+
@test_cases.each do |test_case|
|
108
|
+
runs = test_case.to_xml_nodes
|
109
|
+
runs = runs.last(1) if output_remove_retry_attempts
|
110
|
+
runs.each { |node| testsuite.add_element(node) }
|
111
|
+
end
|
112
|
+
|
113
|
+
# Add sub-suites
|
114
|
+
@sub_suites.each do |sub_suite|
|
115
|
+
testsuite.add_element(sub_suite.to_xml(output_remove_retry_attempts: output_remove_retry_attempts))
|
116
|
+
end
|
117
|
+
|
118
|
+
testsuite
|
119
|
+
end
|
120
|
+
|
121
|
+
def process_children(children)
|
122
|
+
children.each do |child|
|
123
|
+
case child['nodeType']
|
124
|
+
when 'Test Case'
|
125
|
+
# Use from_json to generate multiple test cases if needed
|
126
|
+
@test_cases.concat(TestCase.from_json(node: child))
|
127
|
+
when 'Test Suite', 'Unit test bundle', 'UI test bundle'
|
128
|
+
@sub_suites << TestSuite.from_json(node: child)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|