fastlane-plugin-test_center 3.14.4 → 3.15.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/lib/fastlane/plugin/test_center/actions/collate_junit_reports.rb +17 -11
- data/lib/fastlane/plugin/test_center/actions/multi_scan.rb +63 -14
- data/lib/fastlane/plugin/test_center/actions/tests_from_xctestrun.rb +25 -18
- data/lib/fastlane/plugin/test_center/helper/fastlane_core/device_manager/simulator_extensions.rb +6 -1
- data/lib/fastlane/plugin/test_center/helper/html_test_report.rb +23 -10
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/device_manager.rb +11 -0
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan.rb +0 -53
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/retrying_scan_helper.rb +69 -0
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/runner.rb +51 -5
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/simulator_helper.rb +46 -3
- data/lib/fastlane/plugin/test_center/helper/multi_scan_manager/test_batch_worker_pool.rb +20 -5
- data/lib/fastlane/plugin/test_center/helper/test_collector.rb +3 -3
- data/lib/fastlane/plugin/test_center/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '09ff419b88b430de488e1e2b4053a89b474f355f903c4cc3cfc4df69286da6f8'
|
4
|
+
data.tar.gz: 80114cde688e0efa5f1f5d6b6ee1f0167bd7cc8119598452fb53acd62945c60b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b97fe2075ff7a24dead50f00d3a25028566ead76bb1f01ad95d3dd702ebdf33bff942fad7eff475a82c21a4b3caf892af197300ae8d3f4c8a768bb137f9eba35
|
7
|
+
data.tar.gz: 84fc593aee44b48ca43709ae6401e85546867b3b2502cf8000db752738a40fb24410645fb0243f85797e51f8190e46e9c1e9a69b72610342d4d618fb2f597ff2
|
@@ -8,7 +8,7 @@ module Fastlane
|
|
8
8
|
if report_filepaths.size == 1
|
9
9
|
FileUtils.cp(report_filepaths[0], params[:collated_report])
|
10
10
|
else
|
11
|
-
|
11
|
+
verbose("collate_junit_reports with #{report_filepaths}")
|
12
12
|
reports = report_filepaths.map { |report_filepath| REXML::Document.new(File.new(report_filepath)) }
|
13
13
|
packages = reports.map { |r| r.root.attribute('name').value }.uniq
|
14
14
|
combine_multiple_targets = packages.size > 1
|
@@ -23,7 +23,7 @@ module Fastlane
|
|
23
23
|
increment_testable_tries(target_report.root, report.root)
|
24
24
|
package = report.root.attribute('name').value
|
25
25
|
preprocess_testsuites(report, package, combine_multiple_targets)
|
26
|
-
|
26
|
+
verbose("> collating last report file #{report_filepaths.last}")
|
27
27
|
report.elements.each('//testsuite') do |testsuite|
|
28
28
|
testsuite_name = testsuite.attribute('name').value
|
29
29
|
package_attribute = ''
|
@@ -32,15 +32,15 @@ module Fastlane
|
|
32
32
|
end
|
33
33
|
target_testsuite = REXML::XPath.first(target_report, "//testsuite[@name='#{testsuite_name}' #{package_attribute}]")
|
34
34
|
if target_testsuite
|
35
|
-
|
35
|
+
verbose(" > collating testsuite #{testsuite_name}")
|
36
36
|
collate_testsuite(target_testsuite, testsuite)
|
37
|
-
|
37
|
+
verbose(" < collating testsuite #{testsuite_name}")
|
38
38
|
else
|
39
39
|
testable = REXML::XPath.first(target_report, "//testsuites")
|
40
40
|
testable << testsuite
|
41
41
|
end
|
42
42
|
end
|
43
|
-
|
43
|
+
verbose("< collating last report file #{report_filepaths.last}")
|
44
44
|
end
|
45
45
|
target_report.elements.each('//testsuite') do |testsuite|
|
46
46
|
update_testsuite_counts(testsuite)
|
@@ -83,12 +83,12 @@ module Fastlane
|
|
83
83
|
testsuite_name = testsuite.attribute('name').value
|
84
84
|
duplicate_testsuites = REXML::XPath.match(report, "//testsuite[@name='#{testsuite_name}']")
|
85
85
|
if duplicate_testsuites.size > 1
|
86
|
-
|
86
|
+
verbose(" > flattening_duplicate_testsuites")
|
87
87
|
duplicate_testsuites.drop(1).each do |duplicate_testsuite|
|
88
88
|
collate_testsuite(testsuite, duplicate_testsuite)
|
89
89
|
duplicate_testsuite.parent.delete_element(duplicate_testsuite)
|
90
90
|
end
|
91
|
-
|
91
|
+
verbose(" < flattening_duplicate_testsuites")
|
92
92
|
end
|
93
93
|
update_testsuite_counts(testsuite)
|
94
94
|
end
|
@@ -108,14 +108,14 @@ module Fastlane
|
|
108
108
|
target_testcase = REXML::XPath.first(target_testsuite, "testcase[@name='#{name}' and @classname='#{classname}']")
|
109
109
|
# Replace target_testcase with testcase
|
110
110
|
if target_testcase
|
111
|
-
|
112
|
-
|
111
|
+
verbose(" collate_testsuite with testcase #{name}")
|
112
|
+
verbose(" replacing \"#{target_testcase}\" with \"#{testcase}\"")
|
113
113
|
parent = target_testcase.parent
|
114
114
|
increment_testcase_tries(target_testcase, testcase) unless testcase.root == target_testcase.root
|
115
115
|
parent.insert_after(target_testcase, testcase)
|
116
116
|
parent.delete_element(target_testcase)
|
117
|
-
|
118
|
-
|
117
|
+
verbose("")
|
118
|
+
verbose(" target_testcase after replacement \"#{parent}\"")
|
119
119
|
else
|
120
120
|
target_testsuite << testcase
|
121
121
|
end
|
@@ -165,6 +165,12 @@ module Fastlane
|
|
165
165
|
(value1 + value2).to_s
|
166
166
|
end
|
167
167
|
|
168
|
+
def self.verbose(message)
|
169
|
+
return if ENV.fetch('COLLATE_JUNIT_REPORTS_VERBOSITY', 1).to_i.zero?
|
170
|
+
|
171
|
+
UI.verbose(message)
|
172
|
+
end
|
173
|
+
|
168
174
|
#####################################################
|
169
175
|
# @!group Documentation
|
170
176
|
#####################################################
|
@@ -13,12 +13,7 @@ module Fastlane
|
|
13
13
|
|
14
14
|
class MultiScanAction < Action
|
15
15
|
def self.run(params)
|
16
|
-
params
|
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
|
21
|
-
|
16
|
+
update_interdependent_params(params)
|
22
17
|
strip_leading_and_trailing_whitespace_from_output_types(params)
|
23
18
|
|
24
19
|
warn_of_xcode11_result_bundle_incompatability(params)
|
@@ -28,16 +23,23 @@ module Fastlane
|
|
28
23
|
force_quit_simulator_processes if params[:quit_simulators]
|
29
24
|
|
30
25
|
prepare_for_testing(params.values)
|
26
|
+
|
27
|
+
tests_passed = true
|
28
|
+
summary = {}
|
29
|
+
if params[:build_for_testing]
|
30
|
+
summary = build_summary
|
31
|
+
else
|
32
|
+
coerce_destination_to_array(params)
|
33
|
+
platform = :mac
|
34
|
+
platform = :ios_simulator if Scan.config[:destination].any? { |d| d.include?('platform=iOS Simulator') }
|
31
35
|
|
32
|
-
|
33
|
-
|
34
|
-
|
36
|
+
runner_options = params.values.merge(platform: platform)
|
37
|
+
runner = ::TestCenter::Helper::MultiScanManager::Runner.new(runner_options)
|
38
|
+
tests_passed = runner.run
|
35
39
|
|
36
|
-
|
37
|
-
|
38
|
-
tests_passed = runner.run
|
40
|
+
summary = run_summary(params, tests_passed)
|
41
|
+
end
|
39
42
|
|
40
|
-
summary = run_summary(params, tests_passed)
|
41
43
|
print_run_summary(summary)
|
42
44
|
|
43
45
|
if params[:fail_build] && !tests_passed
|
@@ -46,6 +48,14 @@ module Fastlane
|
|
46
48
|
summary
|
47
49
|
end
|
48
50
|
|
51
|
+
def self.update_interdependent_params(params)
|
52
|
+
params[:quit_simulators] = params._values[:force_quit_simulator] if params._values[:force_quit_simulator]
|
53
|
+
if params[:try_count] < 1
|
54
|
+
UI.important('multi_scan will not test any if :try_count < 0, setting to 1')
|
55
|
+
params[:try_count] = 1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
49
59
|
def self.warn_of_parallelism_with_circle_ci(params)
|
50
60
|
if params[:parallel_testrun_count] > 1 && Helper.is_circle_ci?
|
51
61
|
UI.important("Warning: problems have occurreed when running parallel simulators on Circle CI.")
|
@@ -101,6 +111,16 @@ module Fastlane
|
|
101
111
|
# :nocov:
|
102
112
|
end
|
103
113
|
|
114
|
+
def self.build_summary
|
115
|
+
{
|
116
|
+
result: true,
|
117
|
+
total_tests: 0,
|
118
|
+
passing_testcount: 0,
|
119
|
+
failed_testcount: 0,
|
120
|
+
total_retry_count: 0
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
104
124
|
def self.run_summary(scan_options, tests_passed)
|
105
125
|
scan_options = scan_options.clone
|
106
126
|
|
@@ -439,6 +459,22 @@ module Fastlane
|
|
439
459
|
is_string: false,
|
440
460
|
default_value: true
|
441
461
|
),
|
462
|
+
FastlaneCore::ConfigItem.new(
|
463
|
+
key: :override_scan_options_block,
|
464
|
+
description: 'A block invoked with a Hash of the scan options that will be used when test run is about to start. This allows your code to modify the arguments that will be sent to scan',
|
465
|
+
optional: true,
|
466
|
+
is_string: false,
|
467
|
+
default_value: nil,
|
468
|
+
type: Proc
|
469
|
+
),
|
470
|
+
FastlaneCore::ConfigItem.new(
|
471
|
+
key: :reuse_simulators_for_parallel_testruns,
|
472
|
+
description: 'Find simulators (or clone new ones) that match the requested device for the parallel test runs. This option sets :pre_delete_cloned_simulators to false',
|
473
|
+
optional: true,
|
474
|
+
is_string: false,
|
475
|
+
type: Boolean,
|
476
|
+
default_value: false
|
477
|
+
),
|
442
478
|
FastlaneCore::ConfigItem.new(
|
443
479
|
key: :testrun_completed_block,
|
444
480
|
description: 'A block invoked each time a test run completes. When combined with :parallel_testrun_count, will be called separately in each child process. Return a Hash with :continue set to false to stop retrying tests, or :only_testing to change which tests will be run in the next try',
|
@@ -446,6 +482,14 @@ module Fastlane
|
|
446
482
|
is_string: false,
|
447
483
|
default_value: nil,
|
448
484
|
type: Proc
|
485
|
+
),
|
486
|
+
FastlaneCore::ConfigItem.new(
|
487
|
+
key: :simulator_started_callback,
|
488
|
+
description: 'A block invoked after the iOS simulators have started',
|
489
|
+
optional: true,
|
490
|
+
is_string: false,
|
491
|
+
default_value: nil,
|
492
|
+
type: Proc
|
449
493
|
)
|
450
494
|
]
|
451
495
|
end
|
@@ -475,6 +519,10 @@ module Fastlane
|
|
475
519
|
}
|
476
520
|
end
|
477
521
|
|
522
|
+
sim_callback = lambda do |simulator_device_udid|
|
523
|
+
puts \"Start streaming system log for device \#{simulator_device_udid}\"
|
524
|
+
end
|
525
|
+
|
478
526
|
multi_scan(
|
479
527
|
project: File.absolute_path('../AtomicBoy/AtomicBoy.xcodeproj'),
|
480
528
|
scheme: 'AtomicBoy',
|
@@ -482,7 +530,8 @@ module Fastlane
|
|
482
530
|
batch_count: 4,
|
483
531
|
fail_build: false,
|
484
532
|
parallel_testrun_count: 4,
|
485
|
-
testrun_completed_block: test_run_block
|
533
|
+
testrun_completed_block: test_run_block,
|
534
|
+
simulator_started_callback: sim_callback
|
486
535
|
)
|
487
536
|
",
|
488
537
|
"
|
@@ -139,25 +139,32 @@ module Fastlane
|
|
139
139
|
"
|
140
140
|
require 'fastlane/actions/scan'
|
141
141
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
142
|
+
lane :split_tests do
|
143
|
+
scan(
|
144
|
+
build_for_testing: true,
|
145
|
+
workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
|
146
|
+
scheme: 'AtomicBoy'
|
147
|
+
)
|
148
|
+
derived_data_path = Scan.config[:derived_data_path]
|
149
|
+
xctestrun_file = Dir.glob(\"\#{derived_data_path}/Build/Products/*.xctestrun\").first
|
150
|
+
tests = tests_from_xctestrun(xctestrun: xctestrun_file).values.flatten.shuffle
|
151
|
+
slice_size = (tests.size/4.0).ceil
|
152
|
+
tests.each_slice(slice_size).each_with_index do |inner_array, index|
|
153
|
+
File.write(\"test_output/batch\#{index}.txt\", inner_array.join(','))
|
154
|
+
end
|
155
|
+
end
|
156
156
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
157
|
+
lane :run_split_tests do |options|
|
158
|
+
batch_file = File.join('test_output', \"batch\#{options[:batch_index]}.txt\")
|
159
|
+
only_testing = File.read(batch_file).split(',')
|
160
|
+
multi_scan(
|
161
|
+
workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
|
162
|
+
scheme: 'AtomicBoy',
|
163
|
+
try_count: 3,
|
164
|
+
fail_build: false,
|
165
|
+
only_testing: only_testing
|
166
|
+
)
|
167
|
+
end
|
161
168
|
"
|
162
169
|
]
|
163
170
|
end
|
data/lib/fastlane/plugin/test_center/helper/fastlane_core/device_manager/simulator_extensions.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
|
2
2
|
module FixedCopyLogarchiveFastlaneSimulator
|
3
3
|
def self.included(base)
|
4
|
+
@@log_collection_start_time = DateTime.now
|
4
5
|
base.instance_eval do
|
5
6
|
def copy_logarchive(device, log_identity, logs_destination_dir)
|
6
7
|
require 'shellwords'
|
@@ -8,7 +9,11 @@ module FixedCopyLogarchiveFastlaneSimulator
|
|
8
9
|
logarchive_dst = File.join(logs_destination_dir, "system_logs-#{log_identity}.logarchive")
|
9
10
|
FileUtils.rm_rf(logarchive_dst)
|
10
11
|
FileUtils.mkdir_p(File.expand_path("..", logarchive_dst))
|
11
|
-
|
12
|
+
|
13
|
+
logs_collection_start = @@log_collection_start_time.strftime('%Y-%m-%d %H:%M:%S')
|
14
|
+
command = "xcrun simctl spawn #{device.udid} log collect "
|
15
|
+
command << "--start '#{logs_collection_start}' "
|
16
|
+
command << "--output #{logarchive_dst.shellescape} 2>/dev/null"
|
12
17
|
FastlaneCore::CommandExecutor.execute(command: command, print_all: false, print_command: true)
|
13
18
|
end
|
14
19
|
end
|
@@ -1,6 +1,19 @@
|
|
1
1
|
module TestCenter
|
2
2
|
module Helper
|
3
3
|
module HtmlTestReport
|
4
|
+
|
5
|
+
def self.verbose(message)
|
6
|
+
return if ENV.fetch('COLLATE_HTML_REPORTS_VERBOSITY', 1).to_i.zero?
|
7
|
+
|
8
|
+
FastlaneCore::UI.verbose(message)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.error(message)
|
12
|
+
return if ENV.fetch('COLLATE_HTML_REPORTS_VERBOSITY', 1).to_i.zero?
|
13
|
+
|
14
|
+
FastlaneCore::UI.error(message)
|
15
|
+
end
|
16
|
+
|
4
17
|
class Report
|
5
18
|
require 'rexml/formatters/transitive'
|
6
19
|
|
@@ -24,16 +37,16 @@ module TestCenter
|
|
24
37
|
def collate_report(report)
|
25
38
|
testsuites.each(&:remove_duplicate_testcases)
|
26
39
|
report.testsuites.each(&:remove_duplicate_testcases)
|
27
|
-
|
40
|
+
HtmlTestReport.verbose("TestCenter::Helper::HtmlTestReport::Report.collate_report to report:\n\t#{@root}")
|
28
41
|
report.testsuites.each do |given_testsuite|
|
29
42
|
existing_testsuite = testsuite_with_title(given_testsuite.title)
|
30
43
|
if existing_testsuite.nil?
|
31
|
-
|
44
|
+
HtmlTestReport.verbose("\tadding testsuite\n\t\t#{given_testsuite}")
|
32
45
|
add_testsuite(given_testsuite)
|
33
46
|
else
|
34
|
-
|
47
|
+
HtmlTestReport.verbose("\tcollating testsuite\n\t\t#{given_testsuite.root}")
|
35
48
|
existing_testsuite.collate_testsuite(given_testsuite)
|
36
|
-
|
49
|
+
HtmlTestReport.verbose("\tafter collation exiting testsuite\n\t\t#{existing_testsuite.root}")
|
37
50
|
end
|
38
51
|
end
|
39
52
|
update_test_count
|
@@ -210,15 +223,15 @@ module TestCenter
|
|
210
223
|
given_testcases.each do |given_testcase|
|
211
224
|
existing_testcase = testcase_with_title(given_testcase.title)
|
212
225
|
if existing_testcase.nil?
|
213
|
-
|
226
|
+
HtmlTestReport.verbose("\t\tadding testcase\n\t\t\t#{given_testcase.root}")
|
214
227
|
unless given_testcase.passing?
|
215
|
-
|
228
|
+
HtmlTestReport.verbose("\t\t\twith failure:\n\t\t\t\t#{given_testcase.failure_details}")
|
216
229
|
end
|
217
230
|
add_testcase(given_testcase)
|
218
231
|
else
|
219
|
-
|
232
|
+
HtmlTestReport.verbose("\t\tupdating testcase\n\t\t\t#{existing_testcase.root}")
|
220
233
|
unless given_testcase.passing?
|
221
|
-
|
234
|
+
HtmlTestReport.verbose("\t\t\twith failure:\n\t\t\t\t#{given_testcase.failure_details}")
|
222
235
|
end
|
223
236
|
existing_testcase.update_testcase(given_testcase)
|
224
237
|
end
|
@@ -275,7 +288,7 @@ module TestCenter
|
|
275
288
|
color = row_color
|
276
289
|
failure = failure_details
|
277
290
|
if failure.nil? && !passing?
|
278
|
-
|
291
|
+
HtmlTestReport.error("\t\t\t\tupdating failing test case that does not have failure_details")
|
279
292
|
end
|
280
293
|
parent = @root.parent
|
281
294
|
|
@@ -283,7 +296,7 @@ module TestCenter
|
|
283
296
|
|
284
297
|
new_failure = testcase.failure_details
|
285
298
|
if new_failure && testcase.passing?
|
286
|
-
|
299
|
+
HtmlTestReport.error("\t\t\t\tswapping passing failing test case that _does_have_ failure_details")
|
287
300
|
end
|
288
301
|
|
289
302
|
parent.replace_child(@root, testcase.root)
|
@@ -18,6 +18,17 @@ module FastlaneCore
|
|
18
18
|
self.name = newname
|
19
19
|
end
|
20
20
|
|
21
|
+
def disable_hardware_keyboard
|
22
|
+
UI.verbose("Disabling hardware keyboard for #{self.udid}")
|
23
|
+
plist_filepath = File.expand_path("~/Library/Preferences/com.apple.iphonesimulator.plist")
|
24
|
+
keyboard_pref_key = ":DevicePreferences:#{self.udid}:ConnectHardwareKeyboard"
|
25
|
+
|
26
|
+
command = "/usr/libexec/PlistBuddy -c \"Set #{keyboard_pref_key} false\" #{plist_filepath} 2>/dev/null || "
|
27
|
+
command << "/usr/libexec/PlistBuddy -c \"Add #{keyboard_pref_key} bool false\" #{plist_filepath}"
|
28
|
+
|
29
|
+
`#{command}`
|
30
|
+
end
|
31
|
+
|
21
32
|
def boot
|
22
33
|
return unless is_simulator
|
23
34
|
return unless os_type == "iOS"
|
@@ -7,53 +7,6 @@ module TestCenter
|
|
7
7
|
@retrying_scan_helper = RetryingScanHelper.new(@options)
|
8
8
|
end
|
9
9
|
|
10
|
-
# :nocov:
|
11
|
-
def scan_config
|
12
|
-
Scan.config
|
13
|
-
end
|
14
|
-
|
15
|
-
def scan_cache
|
16
|
-
Scan.cache
|
17
|
-
end
|
18
|
-
# :nocov:
|
19
|
-
|
20
|
-
def prepare_scan_config
|
21
|
-
# this allows multi_scan's `destination` option to be picked up by `scan`
|
22
|
-
scan_config._values.delete(:device)
|
23
|
-
ENV.delete('SCAN_DEVICE')
|
24
|
-
scan_config._values.delete(:devices)
|
25
|
-
ENV.delete('SCAN_DEVICES')
|
26
|
-
# this prevents double -resultBundlePath args to xcodebuild
|
27
|
-
if ReportNameHelper.includes_xcresult?(@options[:output_types])
|
28
|
-
scan_config._values.delete(:result_bundle)
|
29
|
-
ENV.delete('SCAN_RESULT_BUNDLE')
|
30
|
-
end
|
31
|
-
scan_config._values.delete(:skip_testing)
|
32
|
-
scan_cache.clear
|
33
|
-
end
|
34
|
-
|
35
|
-
def update_scan_options
|
36
|
-
valid_scan_keys = Fastlane::Actions::ScanAction.available_options.map(&:key)
|
37
|
-
scan_options = @options.select { |k,v| valid_scan_keys.include?(k) }
|
38
|
-
.merge(@retrying_scan_helper.scan_options)
|
39
|
-
|
40
|
-
prepare_scan_config
|
41
|
-
scan_options[:build_for_testing] = false
|
42
|
-
scan_options.delete(:skip_testing)
|
43
|
-
FastlaneCore::UI.verbose("retrying_scan #update_scan_options")
|
44
|
-
scan_options.each do |k,v|
|
45
|
-
next if v.nil?
|
46
|
-
|
47
|
-
scan_config.set(k,v) unless v.nil?
|
48
|
-
FastlaneCore::UI.verbose("\tSetting #{k.to_s} to #{v}")
|
49
|
-
end
|
50
|
-
if @options[:scan_devices_override]
|
51
|
-
scan_device_names = @options[:scan_devices_override].map { |device| device.name }
|
52
|
-
FastlaneCore::UI.verbose("\tSetting Scan.devices to #{scan_device_names}")
|
53
|
-
Scan.devices.replace(@options[:scan_devices_override])
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
10
|
# :nocov:
|
58
11
|
def self.run(options)
|
59
12
|
RetryingScan.new(options).run
|
@@ -64,12 +17,6 @@ module TestCenter
|
|
64
17
|
try_count = @options[:try_count] || 1
|
65
18
|
begin
|
66
19
|
@retrying_scan_helper.before_testrun
|
67
|
-
update_scan_options
|
68
|
-
|
69
|
-
values = scan_config.values(ask: false)
|
70
|
-
values[:xcode_path] = File.expand_path("../..", FastlaneCore::Helper.xcode_path)
|
71
|
-
ScanHelper.print_scan_parameters(values)
|
72
|
-
|
73
20
|
Scan::Runner.new.run
|
74
21
|
@retrying_scan_helper.after_testrun
|
75
22
|
true
|
@@ -23,9 +23,78 @@ module TestCenter
|
|
23
23
|
delete_xcresults # has to be performed _after_ moving a *.test_result
|
24
24
|
quit_simulator
|
25
25
|
set_json_env
|
26
|
+
set_scan_config
|
26
27
|
print_starting_scan_message
|
27
28
|
end
|
28
29
|
|
30
|
+
def set_scan_config
|
31
|
+
valid_scan_keys = Fastlane::Actions::ScanAction.available_options.map(&:key)
|
32
|
+
new_scan_options = @options.select { |k,v| valid_scan_keys.include?(k) }
|
33
|
+
.merge(scan_options)
|
34
|
+
|
35
|
+
prepare_scan_config
|
36
|
+
new_scan_options[:build_for_testing] = false
|
37
|
+
new_scan_options.delete(:skip_testing)
|
38
|
+
|
39
|
+
new_scan_options = send_callback_override_scan_options_block(new_scan_options)
|
40
|
+
|
41
|
+
FastlaneCore::UI.verbose("retrying_scan #update_scan_options")
|
42
|
+
new_scan_options.each do |k,v|
|
43
|
+
next if v.nil?
|
44
|
+
|
45
|
+
scan_config.set(k,v) unless v.nil?
|
46
|
+
FastlaneCore::UI.verbose("\tSetting #{k.to_s} to #{v}")
|
47
|
+
end
|
48
|
+
if @options[:scan_devices_override]
|
49
|
+
scan_device_names = @options[:scan_devices_override].map { |device| device.name }
|
50
|
+
FastlaneCore::UI.verbose("\tSetting Scan.devices to #{scan_device_names}")
|
51
|
+
if Scan.devices
|
52
|
+
Scan.devices.replace(@options[:scan_devices_override])
|
53
|
+
else
|
54
|
+
Scan.devices = @options[:scan_devices_override]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
values = scan_config.values(ask: false)
|
59
|
+
values[:xcode_path] = File.expand_path("../..", FastlaneCore::Helper.xcode_path)
|
60
|
+
ScanHelper.print_scan_parameters(values)
|
61
|
+
end
|
62
|
+
|
63
|
+
# :nocov:
|
64
|
+
def scan_config
|
65
|
+
Scan.config
|
66
|
+
end
|
67
|
+
|
68
|
+
def scan_cache
|
69
|
+
Scan.cache
|
70
|
+
end
|
71
|
+
# :nocov:
|
72
|
+
|
73
|
+
def prepare_scan_config
|
74
|
+
# this allows multi_scan's `destination` option to be picked up by `scan`
|
75
|
+
scan_config._values.delete(:device)
|
76
|
+
ENV.delete('SCAN_DEVICE')
|
77
|
+
scan_config._values.delete(:devices)
|
78
|
+
ENV.delete('SCAN_DEVICES')
|
79
|
+
# this prevents double -resultBundlePath args to xcodebuild
|
80
|
+
if ReportNameHelper.includes_xcresult?(@options[:output_types])
|
81
|
+
scan_config._values.delete(:result_bundle)
|
82
|
+
ENV.delete('SCAN_RESULT_BUNDLE')
|
83
|
+
end
|
84
|
+
scan_config._values.delete(:skip_testing)
|
85
|
+
scan_cache.clear
|
86
|
+
end
|
87
|
+
|
88
|
+
def send_callback_override_scan_options_block(new_scan_options)
|
89
|
+
return new_scan_options unless @options[:override_scan_options_block]
|
90
|
+
|
91
|
+
callback_result = @options[:override_scan_options_block].call(new_scan_options)
|
92
|
+
if callback_result.kind_of?(Hash)
|
93
|
+
return callback_result
|
94
|
+
end
|
95
|
+
new_scan_options
|
96
|
+
end
|
97
|
+
|
29
98
|
def quit_simulator
|
30
99
|
return unless @options[:quit_simulators]
|
31
100
|
|
@@ -21,6 +21,8 @@ module TestCenter
|
|
21
21
|
update_options_to_use_xcresult_output
|
22
22
|
end
|
23
23
|
@batch_count = 1 # default count. Will be updated by setup_testcollector
|
24
|
+
@options[:parallel_testrun_count] ||= 1
|
25
|
+
@initial_parallel_testrun_count = @options[:parallel_testrun_count]
|
24
26
|
setup_testcollector
|
25
27
|
setup_logcollection
|
26
28
|
FastlaneCore::UI.verbose("< done in TestCenter::Helper::MultiScanManager.initialize")
|
@@ -41,6 +43,7 @@ module TestCenter
|
|
41
43
|
def setup_logcollection
|
42
44
|
FastlaneCore::UI.verbose("> setup_logcollection")
|
43
45
|
return unless @options[:include_simulator_logs]
|
46
|
+
return unless @options[:platform] == :ios_simulator
|
44
47
|
return if Scan::Runner.method_defined?(:prelaunch_simulators)
|
45
48
|
|
46
49
|
# We need to prelaunch the simulators so xcodebuild
|
@@ -66,6 +69,7 @@ module TestCenter
|
|
66
69
|
@test_collector = TestCollector.new(@options)
|
67
70
|
@options.reject! { |key| %i[testplan].include?(key) }
|
68
71
|
@batch_count = @test_collector.batches.size
|
72
|
+
@options[:parallel_testrun_count] = @initial_parallel_testrun_count
|
69
73
|
tests = @test_collector.batches.flatten
|
70
74
|
if tests.size < @options[:parallel_testrun_count].to_i
|
71
75
|
FastlaneCore::UI.important(":parallel_testrun_count greater than the number of tests (#{tests.size}). Reducing to that number.")
|
@@ -89,16 +93,51 @@ module TestCenter
|
|
89
93
|
remove_preexisting_test_result_bundles
|
90
94
|
remote_preexisting_xcresult_bundles
|
91
95
|
|
92
|
-
|
96
|
+
test_results = [false]
|
93
97
|
if should_run_tests_through_single_try?
|
94
|
-
|
98
|
+
test_results.clear
|
99
|
+
setup_run_tests_for_each_device do |device_name|
|
100
|
+
FastlaneCore::UI.message("Single try testing for device '#{device_name}'") if device_name
|
101
|
+
test_results << run_tests_through_single_try
|
102
|
+
end
|
95
103
|
end
|
96
104
|
|
97
|
-
unless
|
105
|
+
unless test_results.all? || @options[:try_count] < 1
|
106
|
+
test_results.clear
|
98
107
|
setup_testcollector
|
99
|
-
|
108
|
+
setup_run_tests_for_each_device do |device_name|
|
109
|
+
FastlaneCore::UI.message("Testing batches for device '#{device_name}'") if device_name
|
110
|
+
test_results << run_test_batches
|
111
|
+
end
|
100
112
|
end
|
101
|
-
|
113
|
+
test_results.all?
|
114
|
+
end
|
115
|
+
|
116
|
+
def setup_run_tests_for_each_device
|
117
|
+
original_output_directory = @options.fetch(:output_directory, 'test_results')
|
118
|
+
unless @options[:platform] == :ios_simulator
|
119
|
+
yield
|
120
|
+
return
|
121
|
+
end
|
122
|
+
|
123
|
+
scan_destinations = Scan.config[:destination].clone
|
124
|
+
try_count = @options[:try_count]
|
125
|
+
|
126
|
+
scan_destinations.each_with_index do |destination, device_index|
|
127
|
+
@options[:try_count] = try_count
|
128
|
+
device_udid_match = destination.match(/id=(?<udid>[^,]+)/)
|
129
|
+
device_udid = device_udid_match[:udid] if device_udid_match
|
130
|
+
if scan_destinations.size > 1
|
131
|
+
@options[:output_directory] = File.join(original_output_directory, device_udid)
|
132
|
+
Scan.config[:destination].replace([destination])
|
133
|
+
end
|
134
|
+
command = "xcrun simctl list devices | grep #{device_udid}"
|
135
|
+
device_info = Fastlane::Actions.sh(command, log: false)
|
136
|
+
|
137
|
+
yield device_info.strip.gsub(/ \(#{device_udid}.*/, '')
|
138
|
+
end
|
139
|
+
Scan.config[:destination].replace(scan_destinations)
|
140
|
+
@options[:output_directory] = original_output_directory
|
102
141
|
end
|
103
142
|
|
104
143
|
def should_run_tests_through_single_try?
|
@@ -148,6 +187,8 @@ module TestCenter
|
|
148
187
|
options = @options.reject { |key| %i[device devices force_quit_simulator].include?(key) }
|
149
188
|
options[:try_count] = 1
|
150
189
|
|
190
|
+
SimulatorHelper.call_simulator_started_callback(@options, Scan.devices)
|
191
|
+
|
151
192
|
tests_passed = RetryingScan.run(options)
|
152
193
|
@options[:try_count] -= 1
|
153
194
|
|
@@ -193,6 +234,11 @@ module TestCenter
|
|
193
234
|
pool_options[:test_batch_results] = test_batch_results
|
194
235
|
pool_options[:xctestrun] = @test_collector.xctestrun_path
|
195
236
|
|
237
|
+
serial_test_batches = (@options.fetch(:parallel_testrun_count, 1) == 1)
|
238
|
+
if serial_test_batches && !@options[:invocation_based_tests]
|
239
|
+
SimulatorHelper.call_simulator_started_callback(@options, Scan.devices)
|
240
|
+
end
|
241
|
+
|
196
242
|
pool = TestBatchWorkerPool.new(pool_options)
|
197
243
|
pool.setup_workers
|
198
244
|
|
@@ -4,6 +4,8 @@ module TestCenter
|
|
4
4
|
class SimulatorHelper
|
5
5
|
def initialize(options)
|
6
6
|
@options = options
|
7
|
+
# TODO: add byebug to make sure we're mocking this
|
8
|
+
@all_simulators = FastlaneCore::DeviceManager.simulators('iOS')
|
7
9
|
end
|
8
10
|
|
9
11
|
def setup
|
@@ -12,6 +14,38 @@ module TestCenter
|
|
12
14
|
end
|
13
15
|
end
|
14
16
|
|
17
|
+
def parallel_destination_simulators
|
18
|
+
remaining_desired_simulators = @options[:parallel_testrun_count] || 0
|
19
|
+
|
20
|
+
simulators = []
|
21
|
+
if @options[:reuse_simulators_for_parallel_testruns]
|
22
|
+
matching_simulators = find_matching_destination_simulators(remaining_desired_simulators)
|
23
|
+
remaining_desired_simulators -= matching_simulators.size
|
24
|
+
(0...matching_simulators.size).each do |s|
|
25
|
+
simulators << [matching_simulators[s]]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if remaining_desired_simulators > 0
|
30
|
+
simulators.concat(clone_destination_simulators(remaining_desired_simulators))
|
31
|
+
end
|
32
|
+
simulators
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_matching_destination_simulators(remaining_desired_simulators)
|
36
|
+
destination = Scan.config[:destination].clone.first
|
37
|
+
|
38
|
+
desired_device = @all_simulators.find do |simulator|
|
39
|
+
match = destination.match(/id=(?<udid>[^,]+)/)
|
40
|
+
match && match[:udid] == simulator.udid
|
41
|
+
end
|
42
|
+
|
43
|
+
matching_simulators = @all_simulators.find_all do |simulator|
|
44
|
+
desired_device.os_version == simulator.os_version && simulator.name =~ /#{Regexp.escape(desired_device.name)} Clone \d #{self.class.name}<[^>]+>/
|
45
|
+
end
|
46
|
+
matching_simulators.first(remaining_desired_simulators)
|
47
|
+
end
|
48
|
+
|
15
49
|
def simulator_matches_destination(simulator, destination)
|
16
50
|
match = destination.match(/id=(?<udid>[^,]+)/)
|
17
51
|
if match
|
@@ -27,12 +61,12 @@ module TestCenter
|
|
27
61
|
found_match
|
28
62
|
end
|
29
63
|
|
30
|
-
def clone_destination_simulators
|
64
|
+
def clone_destination_simulators(remaining_desired_simulators)
|
31
65
|
cloned_simulators = []
|
32
66
|
|
33
|
-
run_count =
|
67
|
+
run_count = remaining_desired_simulators
|
34
68
|
destinations = Scan.config[:destination].clone
|
35
|
-
original_simulators =
|
69
|
+
original_simulators = @all_simulators.find_all do |simulator|
|
36
70
|
found_simulator = destinations.find do |destination|
|
37
71
|
simulator_matches_destination(simulator, destination)
|
38
72
|
end
|
@@ -63,6 +97,15 @@ module TestCenter
|
|
63
97
|
simulator.delete if /#{self.class.name}<\d+>/ =~ simulator.name
|
64
98
|
end
|
65
99
|
end
|
100
|
+
|
101
|
+
def self.call_simulator_started_callback(options, devices)
|
102
|
+
return unless options[:simulator_started_callback]
|
103
|
+
return unless options[:platform] == :ios_simulator
|
104
|
+
|
105
|
+
devices.each do |device|
|
106
|
+
options[:simulator_started_callback].call(device.udid)
|
107
|
+
end
|
108
|
+
end
|
66
109
|
end
|
67
110
|
end
|
68
111
|
end
|
@@ -23,17 +23,23 @@ module TestCenter
|
|
23
23
|
|
24
24
|
@simhelper = SimulatorHelper.new(
|
25
25
|
parallel_testrun_count: @options[:parallel_testrun_count],
|
26
|
-
pre_delete_cloned_simulators: @options.fetch(:pre_delete_cloned_simulators, true)
|
26
|
+
pre_delete_cloned_simulators: @options.fetch(:pre_delete_cloned_simulators, true),
|
27
|
+
reuse_simulators_for_parallel_testruns: @options[:reuse_simulators_for_parallel_testruns] || false
|
27
28
|
)
|
28
29
|
@simhelper.setup
|
29
|
-
@clones = @simhelper.
|
30
|
+
@clones = @simhelper.parallel_destination_simulators
|
30
31
|
main_pid = Process.pid
|
31
|
-
|
32
|
-
|
32
|
+
unless @options[:reuse_simulators_for_parallel_testruns]
|
33
|
+
at_exit do
|
34
|
+
clean_up_cloned_simulators(@clones) if Process.pid == main_pid
|
35
|
+
end
|
33
36
|
end
|
34
37
|
# boot all the simulators _before_ calling `xcodebuilt test` to avoid
|
35
38
|
# testmanagerd connection failures.
|
39
|
+
@clones.flatten.each(&:shutdown)
|
40
|
+
@clones.flatten.each(&:disable_hardware_keyboard)
|
36
41
|
@clones.flatten.each(&:boot)
|
42
|
+
SimulatorHelper.call_simulator_started_callback(@options, @clones.flatten)
|
37
43
|
@clones
|
38
44
|
end
|
39
45
|
|
@@ -63,7 +69,9 @@ module TestCenter
|
|
63
69
|
def parallel_scan_options(worker_index)
|
64
70
|
options = @options.reject { |key| %i[device devices].include?(key) }
|
65
71
|
options[:destination] = destination_for_worker(worker_index)
|
66
|
-
options[:
|
72
|
+
if @options[:platform] == :ios_simulator
|
73
|
+
options[:scan_devices_override] = simulator_devices_for_worker(worker_index)
|
74
|
+
end
|
67
75
|
options[:buildlog_path] = buildlog_path_for_worker(worker_index) if @options[:buildlog_path]
|
68
76
|
options[:derived_data_path] = derived_data_path_for_worker(worker_index)
|
69
77
|
options[:batch_index] = worker_index
|
@@ -89,6 +97,12 @@ module TestCenter
|
|
89
97
|
clones.flatten.each(&:delete)
|
90
98
|
end
|
91
99
|
|
100
|
+
def shutdown_cloned_simulators(clones)
|
101
|
+
return if clones.nil?
|
102
|
+
|
103
|
+
clones.flatten.each(&:shutdown)
|
104
|
+
end
|
105
|
+
|
92
106
|
def setup_serial_workers
|
93
107
|
serial_scan_options = @options.reject { |key| %i[device devices].include?(key) }
|
94
108
|
serial_scan_options[:destination] ||= Scan&.config&.fetch(:destination)
|
@@ -125,6 +139,7 @@ module TestCenter
|
|
125
139
|
busy_workers.map(&:pid).each do |pid|
|
126
140
|
Process.wait(pid)
|
127
141
|
end
|
142
|
+
shutdown_cloned_simulators(@clones)
|
128
143
|
busy_workers.each { |w| w.process_results }
|
129
144
|
end
|
130
145
|
end
|
@@ -116,9 +116,9 @@ module TestCenter
|
|
116
116
|
# with their own testCases.
|
117
117
|
if all_known_tests[testable].to_a.empty?
|
118
118
|
FastlaneCore::UI.verbose("Unable to expand #{testable} to constituent tests")
|
119
|
-
|
119
|
+
expanded_test_identifiers = [testable]
|
120
120
|
else
|
121
|
-
|
121
|
+
expanded_test_identifiers = all_known_tests[testable]
|
122
122
|
end
|
123
123
|
else
|
124
124
|
# this is a testable and a test suite, let's expand it out to all of
|
@@ -131,7 +131,7 @@ module TestCenter
|
|
131
131
|
end
|
132
132
|
end
|
133
133
|
test_identifiers.delete_at(index)
|
134
|
-
test_identifiers.insert(index, *
|
134
|
+
test_identifiers.insert(index, *expanded_test_identifiers)
|
135
135
|
end
|
136
136
|
end
|
137
137
|
|
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.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lyndsey Ferguson
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -310,7 +310,7 @@ homepage: https://github.com/lyndsey-ferguson/fastlane-plugin-test_center
|
|
310
310
|
licenses:
|
311
311
|
- MIT
|
312
312
|
metadata: {}
|
313
|
-
post_install_message:
|
313
|
+
post_install_message:
|
314
314
|
rdoc_options: []
|
315
315
|
require_paths:
|
316
316
|
- lib
|
@@ -326,7 +326,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
326
326
|
version: '0'
|
327
327
|
requirements: []
|
328
328
|
rubygems_version: 3.1.2
|
329
|
-
signing_key:
|
329
|
+
signing_key:
|
330
330
|
specification_version: 4
|
331
331
|
summary: Makes testing your iOS app easier
|
332
332
|
test_files: []
|