fastlane-plugin-test_center 3.10.1 → 3.11.2
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 +9 -2
- data/lib/fastlane/plugin/test_center/actions/multi_scan.rb +10 -2
- data/lib/fastlane/plugin/test_center/actions/test_options_from_testplan.rb +90 -0
- data/lib/fastlane/plugin/test_center/actions/testplans_from_scheme.rb +120 -0
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager.rb +2 -0
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan.rb +5 -2
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb +33 -6
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb +47 -13
- data/lib/fastlane/plugin/test_center/helper/test_collector.rb +31 -1
- data/lib/fastlane/plugin/test_center/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '065618ff56d900cf4b45653bf6985f9f205c200820b4db80d76787bfe8eaa6e9'
|
|
4
|
+
data.tar.gz: 7a2a565897e7a29d16b34318f595b83616c51e97afb5551af0d08ceb2a33b08c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4625cca2677f47cca4d501c16426aa9acc117c0adecbecc1842bb7ef116e99ab100d7827e580639ce18dc3327a4bdbb68d852762320169cffba865448f40fc21
|
|
7
|
+
data.tar.gz: 0abfbc89d1fdc4260b199cd20a7bd8c1b05911510cf628b7339a6bd5ce2790b6c3f6b1d7195677b20e020c8810420c826181621f1dbb47a816ce0c0c8ee2c23a
|
data/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Have you ever spent too much time trying to fix fragile tests only to give up wi
|
|
|
21
21
|
|
|
22
22
|
## Features
|
|
23
23
|
|
|
24
|
-
This plugin makes testing your iOS app easier by providing you actions that give you greater control over everything related to testing your app.
|
|
24
|
+
This plugin makes testing your iOS app easier by providing you actions that give you greater control over everything related to testing your app.
|
|
25
25
|
|
|
26
26
|
`multi_scan` began when I created an action to only re-run the failed tests in order to determine if they were truly failing, or if they were failing randomly due to a fragile infrastructure. This action morphed into an entire plugin with many actions in the testing category.
|
|
27
27
|
|
|
@@ -35,6 +35,8 @@ _read the documentation on each action by clicking on the action name_
|
|
|
35
35
|
| [`suppress_tests_from_junit`](docs/feature_details/suppress_tests_from_junit.md) | suppress tests in an Xcode Scheme using those in a Junit test report | ios, mac |
|
|
36
36
|
| [`suppress_tests`](docs/feature_details/suppress_tests.md) | suppress tests in an Xcode Scheme | ios, mac |
|
|
37
37
|
| [`suppressed_tests`](docs/feature_details/suppressed_tests.md) | returns a list of the suppressed tests in your Xcode Project or Scheme | ios, mac |
|
|
38
|
+
| [`test_options_from_testplan`](docs/feature_details/test_options_from_testplan.md) | returns the tests and test code coverage configuration for a given testplan | ios, mac |
|
|
39
|
+
| [`testplans_from_scheme`](docs/feature_details/testplans_from_scheme.md) | returns the testplans that an Xcode Scheme references | ios, mac |
|
|
38
40
|
| [`tests_from_junit`](docs/feature_details/tests_from_junit.md) | returns the passing and failing tests in a Junit test report | ios, mac |
|
|
39
41
|
| [`tests_from_xctestrun`](docs/feature_details/tests_from_xctestrun.md) | returns a list of tests for each test target in a `xctestrun` file | ios, mac |
|
|
40
42
|
| [`collate_junit_reports`](docs/feature_details/collate_junit_reports.md) | combines multiple Junit test reports into one report | ios, mac |
|
|
@@ -62,7 +64,7 @@ The most popular action in the `test_center` plugin is `multi_scan`, and if you
|
|
|
62
64
|
multi_scan(
|
|
63
65
|
project: File.absolute_path('../AtomicBoy/AtomicBoy.xcodeproj'),
|
|
64
66
|
scheme: 'AtomicBoy',
|
|
65
|
-
try_count: 3, # retry _failing_ tests up to three times^1.
|
|
67
|
+
try_count: 3, # retry _failing_ tests up to three times^1.
|
|
66
68
|
fail_build: false,
|
|
67
69
|
parallel_testrun_count: 4 # run subsets of your tests on parallel simulators^2
|
|
68
70
|
)
|
|
@@ -91,6 +93,11 @@ For more information about how the `fastlane` plugin system works, check out the
|
|
|
91
93
|
|
|
92
94
|
_fastlane_ is the easiest way to automate beta deployments and releases for your iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools).
|
|
93
95
|
|
|
96
|
+
## Supporters
|
|
97
|
+
|
|
98
|
+

|
|
99
|
+
[vdavydovHH](https://github.com/vdavydovHH)
|
|
100
|
+
|
|
94
101
|
## License
|
|
95
102
|
|
|
96
103
|
MIT
|
|
@@ -14,6 +14,10 @@ module Fastlane
|
|
|
14
14
|
class MultiScanAction < Action
|
|
15
15
|
def self.run(params)
|
|
16
16
|
params[:quit_simulators] = params._values[:force_quit_simulator] if params._values[:force_quit_simulator]
|
|
17
|
+
if params[:try_count] < 1
|
|
18
|
+
UI.important('multi_scan will not test any if :try_count < 0, setting to 1')
|
|
19
|
+
params[:try_count] = 1
|
|
20
|
+
end
|
|
17
21
|
|
|
18
22
|
strip_leading_and_trailing_whitespace_from_output_types(params)
|
|
19
23
|
|
|
@@ -228,7 +232,7 @@ module Fastlane
|
|
|
228
232
|
end
|
|
229
233
|
|
|
230
234
|
def self.prepare_scan_options_for_build_for_testing(scan_options)
|
|
231
|
-
build_options = scan_options.merge(build_for_testing: true).reject { |k| k
|
|
235
|
+
build_options = scan_options.merge(build_for_testing: true).reject { |k| %i[test_without_building, testplan].include?(k) }
|
|
232
236
|
Scan.config = FastlaneCore::Configuration.create(
|
|
233
237
|
Fastlane::Actions::ScanAction.available_options,
|
|
234
238
|
ScanHelper.scan_options_from_multi_scan_options(build_options)
|
|
@@ -240,7 +244,11 @@ module Fastlane
|
|
|
240
244
|
end
|
|
241
245
|
|
|
242
246
|
def self.update_xctestrun_after_build(scan_options)
|
|
243
|
-
|
|
247
|
+
glob_pattern = "#{Scan.config[:derived_data_path]}/Build/Products/*.xctestrun"
|
|
248
|
+
if scan_options[:testplan]
|
|
249
|
+
glob_pattern = "#{Scan.config[:derived_data_path]}/Build/Products/*_#{scan_options[:testplan]}_*.xctestrun"
|
|
250
|
+
end
|
|
251
|
+
xctestrun_files = Dir.glob(glob_pattern)
|
|
244
252
|
UI.verbose("After building, found xctestrun files #{xctestrun_files} (choosing 1st)")
|
|
245
253
|
scan_options[:xctestrun] = xctestrun_files.first
|
|
246
254
|
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module Fastlane
|
|
4
|
+
module Actions
|
|
5
|
+
class TestOptionsFromTestplanAction < Action
|
|
6
|
+
def self.run(params)
|
|
7
|
+
testplan_path = params[:testplan]
|
|
8
|
+
|
|
9
|
+
testplan = JSON.load(File.open(testplan_path))
|
|
10
|
+
only_testing = []
|
|
11
|
+
UI.verbose("Examining testplan JSON: #{testplan}")
|
|
12
|
+
testplan['testTargets'].each do |test_target|
|
|
13
|
+
testable = test_target.dig('target', 'name')
|
|
14
|
+
if test_target.key?('selectedTests')
|
|
15
|
+
UI.verbose(" Found selectedTests")
|
|
16
|
+
test_identifiers = test_target['selectedTests'].each do |selected_test|
|
|
17
|
+
UI.verbose(" Found test: '#{selected_test}'")
|
|
18
|
+
only_testing << "#{testable}/#{selected_test.sub('\/', '/')}"
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
UI.verbose(" No selected tests, using testable '#{testable}'")
|
|
22
|
+
only_testing << testable
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
{
|
|
26
|
+
code_coverage: testplan.dig('defaultOptions', 'codeCoverage'),
|
|
27
|
+
only_testing: only_testing
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#####################################################
|
|
32
|
+
# @!group Documentation
|
|
33
|
+
#####################################################
|
|
34
|
+
|
|
35
|
+
def self.description
|
|
36
|
+
"☑️ Gets test info from a given test plan"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.details
|
|
40
|
+
"Gets tests info consisting of tests to run and whether or not code coverage is enabled"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.available_options
|
|
44
|
+
[
|
|
45
|
+
FastlaneCore::ConfigItem.new(
|
|
46
|
+
key: :testplan,
|
|
47
|
+
optional: true,
|
|
48
|
+
env_name: "FL_TEST_OPTIONS_FROM_TESTPLAN_TESTPLAN",
|
|
49
|
+
description: "The Xcode testplan to read the test info from",
|
|
50
|
+
verify_block: proc do |test_plan|
|
|
51
|
+
UI.user_error!("Error: Xcode Test Plan '#{test_plan}' is not valid!") if test_plan and test_plan.empty?
|
|
52
|
+
UI.user_error!("Error: Test Plan does not exist at path '#{test_plan}'") unless File.exist?(test_plan)
|
|
53
|
+
end
|
|
54
|
+
)
|
|
55
|
+
]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.return_value
|
|
59
|
+
"Returns a Hash with keys :code_coverage and :only_testing for the given testplan"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.example_code
|
|
63
|
+
[
|
|
64
|
+
"
|
|
65
|
+
UI.important(
|
|
66
|
+
'example: ' \\
|
|
67
|
+
'get the tests and the test coverage configuration from a given testplan'
|
|
68
|
+
)
|
|
69
|
+
test_options = test_options_from_testplan(
|
|
70
|
+
testplan: 'AtomicBoy/AtomicBoy_2.xctestplan'
|
|
71
|
+
)
|
|
72
|
+
UI.message(\"The AtomicBoy_2 testplan has the following tests: \#{test_options[:only_testing]}\")
|
|
73
|
+
"
|
|
74
|
+
]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.authors
|
|
78
|
+
["lyndsey-ferguson/lyndseydf"]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.category
|
|
82
|
+
:testing
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.is_supported?(platform)
|
|
86
|
+
%i[ios mac].include?(platform)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
module Fastlane
|
|
2
|
+
module Actions
|
|
3
|
+
class TestplansFromSchemeAction < Action
|
|
4
|
+
def self.run(params)
|
|
5
|
+
scheme = params[:scheme]
|
|
6
|
+
scheme_filepaths = schemes_from_project(params[:xcodeproj], scheme) || schemes_from_workspace(params[:workspace], scheme)
|
|
7
|
+
if scheme_filepaths.length.zero?
|
|
8
|
+
UI.user_error!("Error: cannot find any schemes in the Xcode project") if params[:xcodeproj]
|
|
9
|
+
UI.user_error!("Error: cannot find any schemes in the Xcode workspace") if params[:workspace]
|
|
10
|
+
end
|
|
11
|
+
testplan_paths = []
|
|
12
|
+
scheme_filepaths.each do |scheme_filepath|
|
|
13
|
+
UI.verbose("Looking in Scheme '#{scheme_filepath}' for any testplans")
|
|
14
|
+
xcscheme = Xcodeproj::XCScheme.new(scheme_filepath)
|
|
15
|
+
next if xcscheme.test_action.nil?
|
|
16
|
+
next if xcscheme.test_action.testables.to_a.empty?
|
|
17
|
+
next if xcscheme.test_action.testables[0].buildable_references.to_a.empty?
|
|
18
|
+
next if xcscheme.test_action.test_plans.to_a.empty?
|
|
19
|
+
|
|
20
|
+
xcodeproj = xcscheme.test_action.testables[0].buildable_references[0].target_referenced_container.sub('container:', '')
|
|
21
|
+
container_dir = scheme_filepath.sub(/#{xcodeproj}.*/, '')
|
|
22
|
+
xcscheme.test_action.test_plans.each do |testplan|
|
|
23
|
+
testplan_path = File.absolute_path(File.join(container_dir, testplan.target_referenced_container.sub('container:', '')))
|
|
24
|
+
UI.verbose(" found testplan '#{testplan_path}'")
|
|
25
|
+
testplan_paths << testplan_path
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
testplan_paths
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.schemes_from_project(project_path, scheme)
|
|
32
|
+
return nil unless project_path
|
|
33
|
+
|
|
34
|
+
Dir.glob("#{project_path}/{xcshareddata,xcuserdata}/**/xcschemes/#{scheme || '*'}.xcscheme")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.schemes_from_workspace(workspace_path, scheme)
|
|
38
|
+
return nil unless workspace_path
|
|
39
|
+
|
|
40
|
+
xcworkspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_path)
|
|
41
|
+
scheme_filepaths = []
|
|
42
|
+
xcworkspace.file_references.each do |file_reference|
|
|
43
|
+
next if file_reference.path.include?('Pods/Pods.xcodeproj')
|
|
44
|
+
|
|
45
|
+
project_path = file_reference.absolute_path(File.dirname(workspace_path))
|
|
46
|
+
scheme_filepaths.concat(schemes_from_project(project_path, scheme))
|
|
47
|
+
end
|
|
48
|
+
scheme_filepaths
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.description
|
|
52
|
+
"☑️Gets all the testplans that a Scheme references"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.authors
|
|
56
|
+
["lyndsey-ferguson/lyndseydf"]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.available_options
|
|
60
|
+
[
|
|
61
|
+
FastlaneCore::ConfigItem.new(
|
|
62
|
+
key: :xcodeproj,
|
|
63
|
+
env_name: "FL_TESTPLANS_FROM_SCHEME_XCODE_PROJECT",
|
|
64
|
+
optional: true,
|
|
65
|
+
description: "The file path to the Xcode project file that references the Scheme",
|
|
66
|
+
verify_block: proc do |path|
|
|
67
|
+
path = File.expand_path(path.to_s)
|
|
68
|
+
UI.user_error!("Error: Xcode project file path not given!") unless path and !path.empty?
|
|
69
|
+
UI.user_error!("Error: Xcode project '#{path}' not found!") unless Dir.exist?(path)
|
|
70
|
+
end
|
|
71
|
+
),
|
|
72
|
+
FastlaneCore::ConfigItem.new(
|
|
73
|
+
key: :workspace,
|
|
74
|
+
env_name: "FL_TESTPLANS_FROM_SCHEME_XCODE_WORKSPACE",
|
|
75
|
+
optional: true,
|
|
76
|
+
description: "The file path to the Xcode workspace file that references the Scheme",
|
|
77
|
+
verify_block: proc do |value|
|
|
78
|
+
v = File.expand_path(value.to_s)
|
|
79
|
+
UI.user_error!("Error: Workspace file not found at path '#{v}'") unless Dir.exist?(v)
|
|
80
|
+
UI.user_error!("Error: Workspace file is not a workspace, must end with .xcworkspace") unless v.include?(".xcworkspace")
|
|
81
|
+
end
|
|
82
|
+
),
|
|
83
|
+
FastlaneCore::ConfigItem.new(
|
|
84
|
+
key: :scheme,
|
|
85
|
+
optional: true,
|
|
86
|
+
env_name: "FL_TESTPLANS_FROM_SCHEME_XCODE_SCHEME",
|
|
87
|
+
description: "The Xcode scheme referencing the testplan",
|
|
88
|
+
verify_block: proc do |scheme_name|
|
|
89
|
+
UI.user_error!("Error: Xcode Scheme '#{scheme_name}' is not valid!") if scheme_name and scheme_name.empty?
|
|
90
|
+
end
|
|
91
|
+
)
|
|
92
|
+
]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.example_code
|
|
96
|
+
[
|
|
97
|
+
"
|
|
98
|
+
UI.important(
|
|
99
|
+
'example: ' \\
|
|
100
|
+
'get all the testplans that an Xcode Scheme references'
|
|
101
|
+
)
|
|
102
|
+
testplans = testplans_from_scheme(
|
|
103
|
+
xcodeproj: 'AtomicBoy/AtomicBoy.xcodeproj',
|
|
104
|
+
scheme: 'AtomicBoy'
|
|
105
|
+
)
|
|
106
|
+
UI.message(\"The AtomicBoy uses the following testplans: \#{testplans}\")
|
|
107
|
+
"
|
|
108
|
+
]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def self.category
|
|
112
|
+
:testing
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.is_supported?(platform)
|
|
116
|
+
%i[ios mac].include?(platform)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -17,16 +17,18 @@ module TestCenter
|
|
|
17
17
|
end
|
|
18
18
|
# :nocov:
|
|
19
19
|
|
|
20
|
-
def
|
|
20
|
+
def prepare_scan_config
|
|
21
21
|
# this allows multi_scan's `destination` option to be picked up by `scan`
|
|
22
22
|
scan_config._values.delete(:device)
|
|
23
23
|
ENV.delete('SCAN_DEVICE')
|
|
24
24
|
scan_config._values.delete(:devices)
|
|
25
25
|
ENV.delete('SCAN_DEVICES')
|
|
26
|
+
# this prevents double -resultBundlePath args to xcodebuild
|
|
26
27
|
if ReportNameHelper.includes_xcresult?(@options[:output_types])
|
|
27
28
|
scan_config._values.delete(:result_bundle)
|
|
28
29
|
ENV.delete('SCAN_RESULT_BUNDLE')
|
|
29
30
|
end
|
|
31
|
+
scan_config._values.delete(:skip_testing)
|
|
30
32
|
scan_cache.clear
|
|
31
33
|
end
|
|
32
34
|
|
|
@@ -35,8 +37,9 @@ module TestCenter
|
|
|
35
37
|
scan_options = @options.select { |k,v| valid_scan_keys.include?(k) }
|
|
36
38
|
.merge(@retrying_scan_helper.scan_options)
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
prepare_scan_config
|
|
39
41
|
scan_options[:build_for_testing] = false
|
|
42
|
+
scan_options.delete(:skip_testing)
|
|
40
43
|
FastlaneCore::UI.verbose("retrying_scan #update_scan_options")
|
|
41
44
|
scan_options.each do |k,v|
|
|
42
45
|
next if v.nil?
|
|
@@ -45,7 +45,7 @@ module TestCenter
|
|
|
45
45
|
|
|
46
46
|
derived_data_path = File.expand_path(@options[:derived_data_path] || Scan.config[:derived_data_path])
|
|
47
47
|
xcresults = Dir.glob("#{derived_data_path}/Logs/Test/*.xcresult")
|
|
48
|
-
if FastlaneCore::Helper.xcode_at_least?('11
|
|
48
|
+
if FastlaneCore::Helper.xcode_at_least?('11')
|
|
49
49
|
xcresults += Dir.glob("#{output_directory}/*.xcresult")
|
|
50
50
|
end
|
|
51
51
|
FastlaneCore::UI.verbose("Deleting xcresults:")
|
|
@@ -93,6 +93,10 @@ module TestCenter
|
|
|
93
93
|
FastlaneCore::UI.important(":xcargs, #{xcargs}, contained 'build-for-testing', removing it")
|
|
94
94
|
xcargs.slice!('build-for-testing')
|
|
95
95
|
end
|
|
96
|
+
if xcargs.include?('-quiet')
|
|
97
|
+
FastlaneCore::UI.important('Disabling -quiet as failing tests cannot be found with it enabled.')
|
|
98
|
+
xcargs.gsub!('-quiet', '')
|
|
99
|
+
end
|
|
96
100
|
xcargs.gsub!(/-parallel-testing-enabled(=|\s+)(YES|NO)/, '')
|
|
97
101
|
retrying_scan_options = @reportnamer.scan_options.merge(
|
|
98
102
|
{
|
|
@@ -245,7 +249,7 @@ module TestCenter
|
|
|
245
249
|
junit: File.absolute_path(report_filepath)
|
|
246
250
|
}
|
|
247
251
|
)
|
|
248
|
-
@options[:only_testing] = @options[:only_testing] - Fastlane::Actions::TestsFromJunitAction.run(config).fetch(:passing, Hash.new).map(&:shellsafe_testidentifier)
|
|
252
|
+
@options[:only_testing] = (@options[:only_testing] || []) - Fastlane::Actions::TestsFromJunitAction.run(config).fetch(:passing, Hash.new).map(&:shellsafe_testidentifier)
|
|
249
253
|
if @options[:invocation_based_tests]
|
|
250
254
|
@options[:only_testing] = @options[:only_testing].map(&:strip_testcase).uniq
|
|
251
255
|
end
|
|
@@ -288,6 +292,27 @@ module TestCenter
|
|
|
288
292
|
end
|
|
289
293
|
|
|
290
294
|
def retrieve_test_operation_failure(test_session_last_messages)
|
|
295
|
+
if FastlaneCore::Helper.xcode_at_least?('11')
|
|
296
|
+
retrieve_test_operation_failure_post_xcode11(test_session_last_messages)
|
|
297
|
+
else
|
|
298
|
+
retrieve_test_operation_failure_pre_xcode11(test_session_last_messages)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def retrieve_test_operation_failure_post_xcode11(test_session_last_messages)
|
|
303
|
+
if /Connection peer refused channel request/ =~ test_session_last_messages
|
|
304
|
+
test_operation_failure = 'Lost connection to testmanagerd'
|
|
305
|
+
elsif /Please unlock your device and reattach/ =~ test_session_last_messages
|
|
306
|
+
test_operation_failure = 'Test device locked'
|
|
307
|
+
elsif /Test runner exited before starting test execution/ =~ test_session_last_messages
|
|
308
|
+
test_operation_failure = 'Test runner exited before starting test execution'
|
|
309
|
+
else
|
|
310
|
+
test_operation_failure = 'Unknown test operation failure'
|
|
311
|
+
end
|
|
312
|
+
test_operation_failure
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def retrieve_test_operation_failure_pre_xcode11(test_session_last_messages)
|
|
291
316
|
test_operation_failure_match = /Test operation failure: (?<test_operation_failure>.*)$/ =~ test_session_last_messages
|
|
292
317
|
if test_operation_failure_match.nil?
|
|
293
318
|
test_operation_failure = 'Unknown test operation failure'
|
|
@@ -323,15 +348,17 @@ module TestCenter
|
|
|
323
348
|
def move_test_result_bundle_for_next_run
|
|
324
349
|
return unless @options[:result_bundle]
|
|
325
350
|
|
|
326
|
-
|
|
351
|
+
result_extension = FastlaneCore::Helper.xcode_at_least?('11') ? '.xcresult' : '.test_result'
|
|
352
|
+
|
|
353
|
+
glob_pattern = "#{output_directory}/*#{result_extension}"
|
|
327
354
|
preexisting_test_result_bundles = Dir.glob(glob_pattern)
|
|
328
355
|
unnumbered_test_result_bundles = preexisting_test_result_bundles.reject do |test_result|
|
|
329
|
-
test_result =~ /.*-\d
|
|
356
|
+
test_result =~ /.*-\d+\#{result_extension}/
|
|
330
357
|
end
|
|
331
358
|
src_test_bundle = unnumbered_test_result_bundles.first
|
|
332
359
|
dst_test_bundle_parent_dir = File.dirname(src_test_bundle)
|
|
333
|
-
dst_test_bundle_basename = File.basename(src_test_bundle,
|
|
334
|
-
dst_test_bundle = "#{dst_test_bundle_parent_dir}/#{dst_test_bundle_basename}-#{@testrun_count}
|
|
360
|
+
dst_test_bundle_basename = File.basename(src_test_bundle, result_extension)
|
|
361
|
+
dst_test_bundle = "#{dst_test_bundle_parent_dir}/#{dst_test_bundle_basename}-#{@testrun_count}#{result_extension}"
|
|
335
362
|
FastlaneCore::UI.verbose("Moving test_result '#{src_test_bundle}' to '#{dst_test_bundle}'")
|
|
336
363
|
File.rename(src_test_bundle, dst_test_bundle)
|
|
337
364
|
end
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
module TestCenter
|
|
3
2
|
module Helper
|
|
4
3
|
module MultiScanManager
|
|
@@ -7,7 +6,7 @@ module TestCenter
|
|
|
7
6
|
require 'json'
|
|
8
7
|
require 'shellwords'
|
|
9
8
|
require 'snapshot/reset_simulators'
|
|
10
|
-
|
|
9
|
+
|
|
11
10
|
class Runner
|
|
12
11
|
attr_reader :retry_total_count
|
|
13
12
|
|
|
@@ -40,7 +39,8 @@ module TestCenter
|
|
|
40
39
|
return if @options[:invocation_based_tests] && @options[:only_testing].nil?
|
|
41
40
|
return if @test_collector
|
|
42
41
|
|
|
43
|
-
@test_collector =
|
|
42
|
+
@test_collector = TestCollector.new(@options)
|
|
43
|
+
@options.reject! { |key| %i[testplan].include?(key) }
|
|
44
44
|
@batch_count = @test_collector.test_batches.size
|
|
45
45
|
end
|
|
46
46
|
|
|
@@ -66,12 +66,12 @@ module TestCenter
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
unless tests_passed || @options[:try_count] < 1
|
|
69
|
-
setup_testcollector
|
|
69
|
+
setup_testcollector
|
|
70
70
|
tests_passed = run_test_batches
|
|
71
71
|
end
|
|
72
72
|
tests_passed
|
|
73
73
|
end
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
def should_run_tests_through_single_try?
|
|
76
76
|
should_run_for_invocation_tests = @options[:invocation_based_tests] && @options[:only_testing].nil?
|
|
77
77
|
should_run_for_skip_build = @options[:skip_build]
|
|
@@ -114,16 +114,16 @@ module TestCenter
|
|
|
114
114
|
end
|
|
115
115
|
@options[:output_directory] = output_directory
|
|
116
116
|
@options[:destination] = Scan.config[:destination]
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
# We do not want Scan.config to _not_ have :device :devices, we want to
|
|
119
119
|
# use :destination. We remove :force_quit_simulator as we do not want
|
|
120
120
|
# Scan to handle it as multi_scan takes care of it in its own way
|
|
121
121
|
options = @options.reject { |key| %i[device devices force_quit_simulator].include?(key) }
|
|
122
122
|
options[:try_count] = 1
|
|
123
|
-
|
|
123
|
+
|
|
124
124
|
tests_passed = RetryingScan.run(options)
|
|
125
125
|
@options[:try_count] -= 1
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
reportnamer = ReportNameHelper.new(
|
|
128
128
|
@options[:output_types],
|
|
129
129
|
@options[:output_files],
|
|
@@ -138,12 +138,12 @@ module TestCenter
|
|
|
138
138
|
)
|
|
139
139
|
@options[:only_testing] = retrieve_failed_single_try_tests
|
|
140
140
|
@options[:only_testing] = @options[:only_testing].map(&:strip_testcase).uniq
|
|
141
|
-
|
|
141
|
+
|
|
142
142
|
symlink_result_bundle_to_xcresult(output_directory, reportnamer)
|
|
143
143
|
|
|
144
144
|
tests_passed
|
|
145
145
|
end
|
|
146
|
-
|
|
146
|
+
|
|
147
147
|
def retrieve_failed_single_try_tests
|
|
148
148
|
reportnamer = ReportNameHelper.new(
|
|
149
149
|
@options[:output_types],
|
|
@@ -168,10 +168,10 @@ module TestCenter
|
|
|
168
168
|
|
|
169
169
|
pool = TestBatchWorkerPool.new(pool_options)
|
|
170
170
|
pool.setup_workers
|
|
171
|
-
|
|
171
|
+
|
|
172
172
|
remaining_test_batches = @test_collector.test_batches.clone
|
|
173
173
|
remaining_test_batches.each_with_index do |test_batch, current_batch_index|
|
|
174
|
-
worker = pool.wait_for_worker
|
|
174
|
+
worker = pool.wait_for_worker
|
|
175
175
|
FastlaneCore::UI.message("Starting test run #{current_batch_index + 1}")
|
|
176
176
|
worker.run(scan_options_for_worker(test_batch, current_batch_index))
|
|
177
177
|
end
|
|
@@ -189,7 +189,7 @@ module TestCenter
|
|
|
189
189
|
batch: batch_index + 1
|
|
190
190
|
}
|
|
191
191
|
end
|
|
192
|
-
|
|
192
|
+
|
|
193
193
|
def collate_batched_reports
|
|
194
194
|
return unless @batch_count > 1
|
|
195
195
|
return unless @options[:collate_reports]
|
|
@@ -207,10 +207,44 @@ module TestCenter
|
|
|
207
207
|
File.absolute_path(output_directory),
|
|
208
208
|
@test_collector.testables.first
|
|
209
209
|
)
|
|
210
|
+
merge_single_testable_xcresult_with_final_xcresult(report_files_dir, File.absolute_path(output_directory))
|
|
210
211
|
FileUtils.cp_r("#{report_files_dir}/.", File.absolute_path(output_directory))
|
|
211
212
|
FileUtils.rm_rf(report_files_dir)
|
|
212
213
|
end
|
|
213
214
|
|
|
215
|
+
def merge_single_testable_xcresult_with_final_xcresult(testable_output_dir, final_output_dir)
|
|
216
|
+
reportnamer = ReportNameHelper.new(
|
|
217
|
+
@options[:output_types],
|
|
218
|
+
@options[:output_files],
|
|
219
|
+
@options[:custom_report_file_name]
|
|
220
|
+
)
|
|
221
|
+
return unless reportnamer.includes_xcresult?
|
|
222
|
+
|
|
223
|
+
xcresult_bundlename = reportnamer.xcresult_bundlename
|
|
224
|
+
src_xcresult_bundlepath = File.join(testable_output_dir, xcresult_bundlename)
|
|
225
|
+
dst_xcresult_bundlepath = File.join(final_output_dir, xcresult_bundlename)
|
|
226
|
+
|
|
227
|
+
# We do not need to merge if one of these do not exist
|
|
228
|
+
return unless File.exist?(src_xcresult_bundlepath) || File.exist?(dst_xcresult_bundlepath)
|
|
229
|
+
|
|
230
|
+
config = FastlaneCore::Configuration.create(
|
|
231
|
+
Fastlane::Actions::CollateXcresultsAction.available_options,
|
|
232
|
+
{
|
|
233
|
+
xcresults: [src_xcresult_bundlepath, dst_xcresult_bundlepath],
|
|
234
|
+
collated_xcresult: dst_xcresult_bundlepath
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
FastlaneCore::UI.verbose("Merging xcresult '#{src_xcresult_bundlepath}' to '#{dst_xcresult_bundlepath}'")
|
|
238
|
+
Fastlane::Actions::CollateXcresultsAction.run(config)
|
|
239
|
+
FileUtils.rm_rf(src_xcresult_bundlepath)
|
|
240
|
+
if @result_bundle_desired
|
|
241
|
+
xcresult_bundlename = reportnamer.xcresult_bundlename
|
|
242
|
+
test_result_bundlename = File.basename(xcresult_bundlename, '.*') + '.test_result'
|
|
243
|
+
test_result_bundlename_path = File.join(testable_output_dir, test_result_bundlename)
|
|
244
|
+
FileUtils.rm_rf(test_result_bundlename_path)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
214
248
|
def symlink_result_bundle_to_xcresult(output_dir, reportname_helper)
|
|
215
249
|
return unless @result_bundle_desired && reportname_helper.includes_xcresult?
|
|
216
250
|
|
|
@@ -15,7 +15,7 @@ module TestCenter
|
|
|
15
15
|
unless @xctestrun_path && File.exist?(@xctestrun_path)
|
|
16
16
|
FastlaneCore::UI.user_error!("Error: cannot find xctestrun file '#{@xctestrun_path}'")
|
|
17
17
|
end
|
|
18
|
-
@only_testing = options[:only_testing]
|
|
18
|
+
@only_testing = options[:only_testing] || only_testing_from_testplan(options)
|
|
19
19
|
if @only_testing.kind_of?(String)
|
|
20
20
|
@only_testing = @only_testing.split(',')
|
|
21
21
|
end
|
|
@@ -27,6 +27,36 @@ module TestCenter
|
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
def only_testing_from_testplan(options)
|
|
31
|
+
return unless options[:testplan] && options[:scheme]
|
|
32
|
+
|
|
33
|
+
config = FastlaneCore::Configuration.create(
|
|
34
|
+
Fastlane::Actions::TestplansFromSchemeAction.available_options,
|
|
35
|
+
{
|
|
36
|
+
workspace: options[:workspace],
|
|
37
|
+
xcodeproj: options[:project],
|
|
38
|
+
scheme: options[:scheme]
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
testplans = Fastlane::Actions::TestplansFromSchemeAction.run(config)
|
|
42
|
+
FastlaneCore::UI.verbose("TestCollector found testplans: #{testplans}")
|
|
43
|
+
testplan = testplans.find do |testplan_path|
|
|
44
|
+
%r{(.*/?#{ options[:testplan] })\.xctestplan}.match?(testplan_path)
|
|
45
|
+
end
|
|
46
|
+
FastlaneCore::UI.verbose(" using :testplan option, #{options[:testplan]}, using found one: #{testplan}")
|
|
47
|
+
|
|
48
|
+
return if testplan.nil?
|
|
49
|
+
|
|
50
|
+
config = FastlaneCore::Configuration.create(
|
|
51
|
+
Fastlane::Actions::TestOptionsFromTestplanAction.available_options,
|
|
52
|
+
{
|
|
53
|
+
testplan: testplan
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
test_options = Fastlane::Actions::TestOptionsFromTestplanAction.run(config)
|
|
57
|
+
return test_options[:only_testing]
|
|
58
|
+
end
|
|
59
|
+
|
|
30
60
|
def default_derived_data_path(options)
|
|
31
61
|
project_derived_data_path = Scan.project.build_settings(key: "BUILT_PRODUCTS_DIR")
|
|
32
62
|
File.expand_path("../../..", project_derived_data_path)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fastlane-plugin-test_center
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.11.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lyndsey Ferguson
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-
|
|
11
|
+
date: 2020-06-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|
|
@@ -267,6 +267,8 @@ files:
|
|
|
267
267
|
- lib/fastlane/plugin/test_center/actions/suppress_tests.rb
|
|
268
268
|
- lib/fastlane/plugin/test_center/actions/suppress_tests_from_junit.rb
|
|
269
269
|
- lib/fastlane/plugin/test_center/actions/suppressed_tests.rb
|
|
270
|
+
- lib/fastlane/plugin/test_center/actions/test_options_from_testplan.rb
|
|
271
|
+
- lib/fastlane/plugin/test_center/actions/testplans_from_scheme.rb
|
|
270
272
|
- lib/fastlane/plugin/test_center/actions/tests_from_junit.rb
|
|
271
273
|
- lib/fastlane/plugin/test_center/actions/tests_from_xctestrun.rb
|
|
272
274
|
- lib/fastlane/plugin/test_center/helper/html_test_report.rb
|