fastlane-plugin-test_center 3.14.3 → 3.14.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e081e2a0d9d6bcb59c38f24072eb0059edef4457f03fed6e2318930260e9f5a4
4
- data.tar.gz: 681403d6200f5d943e28f9c3fa6b90a6f450d7a9252c476eb7cf77c7e0c6101d
3
+ metadata.gz: 290ab3e6fb5932555e74d834784e80cb441dc4b7e4feb49eda479382a603a1e5
4
+ data.tar.gz: f46250359e1d156d283dd033e6e8db8c851297ea7a6385d78da4e72a014d0ebd
5
5
  SHA512:
6
- metadata.gz: 98e979a4fbafe31e6dc33bd1a3a94a1e1da316ce8226c3688e9d223461a33c5d75c9291dc837e256c227915959547f8964df38c524e40b45742408235ee94713
7
- data.tar.gz: 3049728d26e108dea44480f7445e41be419f4189e3bb2b63b8cef7871a9d7bf58b5edcee01351dc40496209f6805706bfcb0b6cf08b9fee6c43bba1decbfecda
6
+ metadata.gz: aaf987944f48d6511755077c7e4e1c564f722ad8c7f69f8c4e0f24b7c5db62590edc9fc69372e98d19e0cc8b7466f8b3fd2ef2a1ddba472b37bc45c3ee0b12a5
7
+ data.tar.gz: e1eb52e0b8b396019fe9727af6895948ad675c574b80293c866dfd627752d0e2dc3c8dd11b3e8f7508a40f1f64732a4b2987b1f92938aa6cf21b98d9c0d731d7
data/README.md CHANGED
@@ -99,7 +99,7 @@ _fastlane_ is the easiest way to automate beta deployments and releases for your
99
99
  ## Supporters
100
100
 
101
101
  ![Владислав Давыдов](https://avatars1.githubusercontent.com/u/47553334?s=44&u=4691860dba898943b947180b3d28bb85851b0d7c&v=4)
102
- [vdavydovHH](https://github.com/vdavydovHH)
102
+ [vdavydovHH](https://github.com/vdavydovHH) ![amelendezSGY](https://avatars1.githubusercontent.com/u/28657466?s=44&v=4)[amelendezSGY](https://github.com/amelendezSGY)
103
103
 
104
104
  ## License
105
105
 
@@ -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
- UI.verbose("collate_junit_reports with #{report_filepaths}")
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
- UI.verbose("> collating last report file #{report_filepaths.last}")
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
- UI.verbose(" > collating testsuite #{testsuite_name}")
35
+ verbose(" > collating testsuite #{testsuite_name}")
36
36
  collate_testsuite(target_testsuite, testsuite)
37
- UI.verbose(" < collating testsuite #{testsuite_name}")
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
- UI.verbose("< collating last report file #{report_filepaths.last}")
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
- UI.verbose(" > flattening_duplicate_testsuites")
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
- UI.verbose(" < flattening_duplicate_testsuites")
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
- UI.verbose(" collate_testsuite with testcase #{name}")
112
- UI.verbose(" replacing \"#{target_testcase}\" with \"#{testcase}\"")
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
- UI.verbose("")
118
- UI.verbose(" target_testcase after replacement \"#{parent}\"")
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
  #####################################################
@@ -28,16 +28,23 @@ module Fastlane
28
28
  force_quit_simulator_processes if params[:quit_simulators]
29
29
 
30
30
  prepare_for_testing(params.values)
31
+
32
+ tests_passed = true
33
+ summary = {}
34
+ if params[:build_for_testing]
35
+ summary = build_summary
36
+ else
37
+ coerce_destination_to_array(params)
38
+ platform = :mac
39
+ platform = :ios_simulator if Scan.config[:destination].any? { |d| d.include?('platform=iOS Simulator') }
31
40
 
32
- coerce_destination_to_array(params)
33
- platform = :mac
34
- platform = :ios_simulator if Scan.config[:destination].any? { |d| d.include?('platform=iOS Simulator') }
41
+ runner_options = params.values.merge(platform: platform)
42
+ runner = ::TestCenter::Helper::MultiScanManager::Runner.new(runner_options)
43
+ tests_passed = runner.run
35
44
 
36
- runner_options = params.values.merge(platform: platform)
37
- runner = ::TestCenter::Helper::MultiScanManager::Runner.new(runner_options)
38
- tests_passed = runner.run
45
+ summary = run_summary(params, tests_passed)
46
+ end
39
47
 
40
- summary = run_summary(params, tests_passed)
41
48
  print_run_summary(summary)
42
49
 
43
50
  if params[:fail_build] && !tests_passed
@@ -101,6 +108,16 @@ module Fastlane
101
108
  # :nocov:
102
109
  end
103
110
 
111
+ def self.build_summary
112
+ {
113
+ result: true,
114
+ total_tests: 0,
115
+ passing_testcount: 0,
116
+ failed_testcount: 0,
117
+ total_retry_count: 0
118
+ }
119
+ end
120
+
104
121
  def self.run_summary(scan_options, tests_passed)
105
122
  scan_options = scan_options.clone
106
123
 
@@ -361,6 +378,14 @@ module Fastlane
361
378
  UI.user_error!("Error: Batch counts must be greater than zero") unless count > 0
362
379
  end
363
380
  ),
381
+ FastlaneCore::ConfigItem.new(
382
+ key: :batches,
383
+ env_name: "FL_MULTI_SCAN_BATCHES",
384
+ description: "The explicit batches (an Array of Arrays of test identifiers) to run either serially, or each batch on a simulator in parallel if :parallel_testrun_count is given",
385
+ type: Array,
386
+ optional: true,
387
+ conflicting_options: [:batch_count]
388
+ ),
364
389
  FastlaneCore::ConfigItem.new(
365
390
  key: :retry_test_runner_failures,
366
391
  description: "Set to true If you want to treat build failures during testing, like 'Test runner exited before starting test execution', as 'all tests failed'",
@@ -375,7 +400,7 @@ module Fastlane
375
400
  is_string: false,
376
401
  default_value: false,
377
402
  optional: true,
378
- conflicting_options: [:batch_count],
403
+ conflicting_options: %i[batch_count batches],
379
404
  conflict_block: proc do |value|
380
405
  UI.user_error!(
381
406
  "Error: Can't use 'invocation_based_tests' and 'batch_count' options in one run, "\
@@ -433,7 +458,7 @@ module Fastlane
433
458
  ),
434
459
  FastlaneCore::ConfigItem.new(
435
460
  key: :testrun_completed_block,
436
- description: 'A block invoked each time a test run completes. When combined with :parallel_testrun_count, will be called separately in each child process',
461
+ 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',
437
462
  optional: true,
438
463
  is_string: false,
439
464
  default_value: nil,
@@ -461,6 +486,10 @@ module Fastlane
461
486
 
462
487
  # UI.abort_with_message!('You could conditionally abort')
463
488
  UI.message(\"\\\u1F60A everything is fine, let's continue try \#{try_attempt + 1} for batch \#{batch}\")
489
+ {
490
+ continue: true,
491
+ only_testing: ['AtomicBoyUITests/AtomicBoyUITests/testExample17']
492
+ }
464
493
  end
465
494
 
466
495
  multi_scan(
@@ -486,6 +515,120 @@ module Fastlane
486
515
  output_files: 'report.json',
487
516
  fail_build: false
488
517
  )
518
+ ",
519
+ "
520
+ UI.header('batches feature')
521
+ multi_scan(
522
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
523
+ scheme: 'AtomicBoy',
524
+ try_count: 3,
525
+ fail_build: false,
526
+ batches: [
527
+ [
528
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample5',
529
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample10',
530
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample15'
531
+ ],
532
+ [
533
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample6',
534
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample12',
535
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample18'
536
+ ]
537
+ ]
538
+ )
539
+ "
540
+ ]
541
+ end
542
+
543
+ def self.integration_tests
544
+ [
545
+ "
546
+ UI.header('Basic test')
547
+ multi_scan(
548
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
549
+ scheme: 'AtomicBoy',
550
+ fail_build: false,
551
+ try_count: 2,
552
+ disable_xcpretty: true
553
+ )
554
+ ",
555
+ "
556
+ UI.header('Basic test with 1 specific test')
557
+ multi_scan(
558
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
559
+ scheme: 'AtomicBoy',
560
+ fail_build: false,
561
+ try_count: 2,
562
+ only_testing: ['AtomicBoyUITests/AtomicBoyUITests/testExample']
563
+ )
564
+ ",
565
+ "
566
+ UI.header('Basic test with test target expansion')
567
+ multi_scan(
568
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
569
+ scheme: 'AtomicBoy',
570
+ fail_build: false,
571
+ try_count: 2,
572
+ only_testing: ['AtomicBoyUITests', 'AtomicBoyTests']
573
+ )
574
+ ",
575
+ "
576
+ UI.header('Parallel test run')
577
+ multi_scan(
578
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
579
+ scheme: 'AtomicBoy',
580
+ fail_build: false,
581
+ try_count: 2,
582
+ parallel_testrun_count: 2
583
+ )
584
+ ",
585
+ "
586
+ UI.header('Parallel test run with fewer tests than parallel test runs')
587
+ multi_scan(
588
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
589
+ scheme: 'AtomicBoy',
590
+ fail_build: false,
591
+ try_count: 2,
592
+ parallel_testrun_count: 4,
593
+ only_testing: ['AtomicBoyUITests/AtomicBoyUITests/testExample']
594
+ )
595
+ ",
596
+ "
597
+ UI.header('Basic test with batch count')
598
+ multi_scan(
599
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
600
+ scheme: 'AtomicBoy',
601
+ fail_build: false,
602
+ try_count: 2,
603
+ batch_count: 2
604
+ )
605
+ ",
606
+ "
607
+ UI.header('Basic test with batches')
608
+ multi_scan(
609
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
610
+ scheme: 'AtomicBoy',
611
+ fail_build: false,
612
+ try_count: 2,
613
+ batches: [
614
+ ['AtomicBoyUITests/AtomicBoyUITests/testExample', 'AtomicBoyUITests/AtomicBoyUITests/testExample2'],
615
+ ['AtomicBoyUITests/AtomicBoyUITests/testExample3', 'AtomicBoyUITests/AtomicBoyUITests/testExample4']
616
+ ],
617
+ parallel_testrun_count: 2
618
+ )
619
+ ",
620
+ "
621
+ UI.header('Basic test with xcresult')
622
+ multi_scan(
623
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
624
+ scheme: 'AtomicBoy',
625
+ output_types: 'xcresult',
626
+ output_files: 'result.xcresult',
627
+ collate_reports: false,
628
+ fail_build: false,
629
+ try_count: 2,
630
+ batch_count: 2
631
+ )
489
632
  "
490
633
  ]
491
634
  end
@@ -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
- command = "xcrun simctl spawn #{device.udid} log collect --output #{logarchive_dst.shellescape} 2>/dev/null"
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,13 @@
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
+
4
11
  class Report
5
12
  require 'rexml/formatters/transitive'
6
13
 
@@ -24,16 +31,16 @@ module TestCenter
24
31
  def collate_report(report)
25
32
  testsuites.each(&:remove_duplicate_testcases)
26
33
  report.testsuites.each(&:remove_duplicate_testcases)
27
- FastlaneCore::UI.verbose("TestCenter::Helper::HtmlTestReport::Report.collate_report to report:\n\t#{@root}")
34
+ HtmlTestReport.verbose("TestCenter::Helper::HtmlTestReport::Report.collate_report to report:\n\t#{@root}")
28
35
  report.testsuites.each do |given_testsuite|
29
36
  existing_testsuite = testsuite_with_title(given_testsuite.title)
30
37
  if existing_testsuite.nil?
31
- FastlaneCore::UI.verbose("\tadding testsuite\n\t\t#{given_testsuite}")
38
+ HtmlTestReport.verbose("\tadding testsuite\n\t\t#{given_testsuite}")
32
39
  add_testsuite(given_testsuite)
33
40
  else
34
- FastlaneCore::UI.verbose("\tcollating testsuite\n\t\t#{given_testsuite.root}")
41
+ HtmlTestReport.verbose("\tcollating testsuite\n\t\t#{given_testsuite.root}")
35
42
  existing_testsuite.collate_testsuite(given_testsuite)
36
- FastlaneCore::UI.verbose("\tafter collation exiting testsuite\n\t\t#{existing_testsuite.root}")
43
+ HtmlTestReport.verbose("\tafter collation exiting testsuite\n\t\t#{existing_testsuite.root}")
37
44
  end
38
45
  end
39
46
  update_test_count
@@ -210,15 +217,15 @@ module TestCenter
210
217
  given_testcases.each do |given_testcase|
211
218
  existing_testcase = testcase_with_title(given_testcase.title)
212
219
  if existing_testcase.nil?
213
- FastlaneCore::UI.verbose("\t\tadding testcase\n\t\t\t#{given_testcase.root}")
220
+ HtmlTestReport.verbose("\t\tadding testcase\n\t\t\t#{given_testcase.root}")
214
221
  unless given_testcase.passing?
215
- FastlaneCore::UI.verbose("\t\t\twith failure:\n\t\t\t\t#{given_testcase.failure_details}")
222
+ HtmlTestReport.verbose("\t\t\twith failure:\n\t\t\t\t#{given_testcase.failure_details}")
216
223
  end
217
224
  add_testcase(given_testcase)
218
225
  else
219
- FastlaneCore::UI.verbose("\t\tupdating testcase\n\t\t\t#{existing_testcase.root}")
226
+ HtmlTestReport.verbose("\t\tupdating testcase\n\t\t\t#{existing_testcase.root}")
220
227
  unless given_testcase.passing?
221
- FastlaneCore::UI.verbose("\t\t\twith failure:\n\t\t\t\t#{given_testcase.failure_details}")
228
+ HtmlTestReport.verbose("\t\t\twith failure:\n\t\t\t\t#{given_testcase.failure_details}")
222
229
  end
223
230
  existing_testcase.update_testcase(given_testcase)
224
231
  end
@@ -275,7 +282,7 @@ module TestCenter
275
282
  color = row_color
276
283
  failure = failure_details
277
284
  if failure.nil? && !passing?
278
- FastlaneCore::UI.error("\t\t\t\tupdating failing test case that does not have failure_details")
285
+ HtmlTestReport.error("\t\t\t\tupdating failing test case that does not have failure_details")
279
286
  end
280
287
  parent = @root.parent
281
288
 
@@ -283,7 +290,7 @@ module TestCenter
283
290
 
284
291
  new_failure = testcase.failure_details
285
292
  if new_failure && testcase.passing?
286
- FastlaneCore::UI.error("\t\t\t\tswapping passing failing test case that _does_have_ failure_details")
293
+ HtmlTestReport.error("\t\t\t\tswapping passing failing test case that _does_have_ failure_details")
287
294
  end
288
295
 
289
296
  parent.replace_child(@root, testcase.root)
@@ -50,7 +50,11 @@ module TestCenter
50
50
  if @options[:scan_devices_override]
51
51
  scan_device_names = @options[:scan_devices_override].map { |device| device.name }
52
52
  FastlaneCore::UI.verbose("\tSetting Scan.devices to #{scan_device_names}")
53
- Scan.devices.replace(@options[:scan_devices_override])
53
+ if Scan.devices
54
+ Scan.devices.replace(@options[:scan_devices_override])
55
+ else
56
+ Scan.devices = @options[:scan_devices_override]
57
+ end
54
58
  end
55
59
  end
56
60
 
@@ -16,6 +16,7 @@ module TestCenter
16
16
  @options[:output_files],
17
17
  @options[:custom_report_file_name]
18
18
  )
19
+ @callback_overrides_only_testing = false
19
20
  end
20
21
 
21
22
  def before_testrun
@@ -189,7 +190,26 @@ module TestCenter
189
190
  update_json_failure_details(info)
190
191
  update_test_result_bundle_details(info)
191
192
 
192
- @options[:testrun_completed_block].call(info)
193
+ @callback_overrides_only_testing = false
194
+ callback_result = @options[:testrun_completed_block].call(info)
195
+ if callback_result.kind_of?(Hash)
196
+ should_continue = callback_result.fetch(:continue, true)
197
+ if !should_continue
198
+ discontinue_message = 'Following testrun_completed_block\'s request to discontinue testing'
199
+ discontinue_message << " for batch ##{@options[:batch]}" unless @options[:batch].nil?
200
+ FastlaneCore::UI.verbose(discontinue_message)
201
+ @testrun_count = options[:try_count]
202
+ end
203
+ overridden_only_testing = callback_result.fetch(:only_testing, nil)
204
+ if overridden_only_testing && should_continue
205
+ override_only_testing_message = 'Following testrun_completed_block\'s request to change :only_testing to '
206
+ override_only_testing_message << overridden_only_testing.to_s
207
+ override_only_testing_message << " for batch ##{@options[:batch]}" unless @options[:batch].nil?
208
+ FastlaneCore::UI.verbose(override_only_testing_message)
209
+ @callback_overrides_only_testing = true
210
+ @options[:only_testing] = overridden_only_testing
211
+ end
212
+ end
193
213
  end
194
214
 
195
215
  def failure_details(additional_info)
@@ -242,6 +262,8 @@ module TestCenter
242
262
  end
243
263
 
244
264
  def update_only_testing
265
+ return if @callback_overrides_only_testing
266
+
245
267
  report_filepath = File.join(output_directory, @reportnamer.junit_last_reportname)
246
268
  config = FastlaneCore::Configuration.create(
247
269
  Fastlane::Actions::TestsFromJunitAction.available_options,
@@ -41,6 +41,7 @@ module TestCenter
41
41
  def setup_logcollection
42
42
  FastlaneCore::UI.verbose("> setup_logcollection")
43
43
  return unless @options[:include_simulator_logs]
44
+ return unless @options[:platform] == :ios_simulator
44
45
  return if Scan::Runner.method_defined?(:prelaunch_simulators)
45
46
 
46
47
  # We need to prelaunch the simulators so xcodebuild
@@ -65,8 +66,8 @@ module TestCenter
65
66
 
66
67
  @test_collector = TestCollector.new(@options)
67
68
  @options.reject! { |key| %i[testplan].include?(key) }
68
- @batch_count = @test_collector.test_batches.size
69
- tests = @test_collector.test_batches.flatten
69
+ @batch_count = @test_collector.batches.size
70
+ tests = @test_collector.batches.flatten
70
71
  if tests.size < @options[:parallel_testrun_count].to_i
71
72
  FastlaneCore::UI.important(":parallel_testrun_count greater than the number of tests (#{tests.size}). Reducing to that number.")
72
73
  @options[:parallel_testrun_count] = tests.size
@@ -89,16 +90,51 @@ module TestCenter
89
90
  remove_preexisting_test_result_bundles
90
91
  remote_preexisting_xcresult_bundles
91
92
 
92
- tests_passed = false
93
+ test_results = [false]
93
94
  if should_run_tests_through_single_try?
94
- tests_passed = run_tests_through_single_try
95
+ test_results.clear
96
+ setup_run_tests_for_each_device do |device_name|
97
+ FastlaneCore::UI.message("Single try testing for device '#{device_name}'") if device_name
98
+ test_results << run_tests_through_single_try
99
+ end
95
100
  end
96
101
 
97
- unless tests_passed || @options[:try_count] < 1
102
+ unless test_results.all? || @options[:try_count] < 1
103
+ test_results.clear
98
104
  setup_testcollector
99
- tests_passed = run_test_batches
105
+ setup_run_tests_for_each_device do |device_name|
106
+ FastlaneCore::UI.message("Testing batches for device '#{device_name}'") if device_name
107
+ test_results << run_test_batches
108
+ end
100
109
  end
101
- tests_passed
110
+ test_results.all?
111
+ end
112
+
113
+ def setup_run_tests_for_each_device
114
+ original_output_directory = @options.fetch(:output_directory, 'test_results')
115
+ unless @options[:platform] == :ios_simulator
116
+ yield
117
+ return
118
+ end
119
+
120
+ scan_destinations = Scan.config[:destination].clone
121
+ try_count = @options[:try_count]
122
+
123
+ scan_destinations.each_with_index do |destination, device_index|
124
+ @options[:try_count] = try_count
125
+ device_udid_match = destination.match(/id=(?<udid>[^,]+)/)
126
+ device_udid = device_udid_match[:udid] if device_udid_match
127
+ if scan_destinations.size > 1
128
+ @options[:output_directory] = File.join(original_output_directory, device_udid)
129
+ Scan.config[:destination].replace([destination])
130
+ end
131
+ command = "xcrun simctl list devices | grep #{device_udid}"
132
+ device_info = Fastlane::Actions.sh(command, log: false)
133
+
134
+ yield device_info.strip.gsub(/ \(#{device_udid}.*/, '')
135
+ end
136
+ Scan.config[:destination].replace(scan_destinations)
137
+ @options[:output_directory] = original_output_directory
102
138
  end
103
139
 
104
140
  def should_run_tests_through_single_try?
@@ -196,7 +232,7 @@ module TestCenter
196
232
  pool = TestBatchWorkerPool.new(pool_options)
197
233
  pool.setup_workers
198
234
 
199
- remaining_test_batches = @test_collector.test_batches.clone
235
+ remaining_test_batches = @test_collector.batches.clone
200
236
  remaining_test_batches.each_with_index do |test_batch, current_batch_index|
201
237
  worker = pool.wait_for_worker
202
238
  FastlaneCore::UI.message("Starting test run #{current_batch_index + 1}")
@@ -209,7 +245,7 @@ module TestCenter
209
245
  end
210
246
 
211
247
  def scan_options_for_worker(test_batch, batch_index)
212
- if @test_collector.test_batches.size > 1
248
+ if @test_collector.batches.size > 1
213
249
  # If there are more than 1 batch, then we want each batch result
214
250
  # sent to a "batch index" output folder to be collated later
215
251
  # into the requested output_folder.
@@ -63,7 +63,9 @@ module TestCenter
63
63
  def parallel_scan_options(worker_index)
64
64
  options = @options.reject { |key| %i[device devices].include?(key) }
65
65
  options[:destination] = destination_for_worker(worker_index)
66
- options[:scan_devices_override] = simulator_devices_for_worker(worker_index)
66
+ if @options[:platform] == :ios_simulator
67
+ options[:scan_devices_override] = simulator_devices_for_worker(worker_index)
68
+ end
67
69
  options[:buildlog_path] = buildlog_path_for_worker(worker_index) if @options[:buildlog_path]
68
70
  options[:derived_data_path] = derived_data_path_for_worker(worker_index)
69
71
  options[:batch_index] = worker_index
@@ -89,6 +91,12 @@ module TestCenter
89
91
  clones.flatten.each(&:delete)
90
92
  end
91
93
 
94
+ def shutdown_cloned_simulators(clones)
95
+ return if clones.nil?
96
+
97
+ clones.flatten.each(&:shutdown)
98
+ end
99
+
92
100
  def setup_serial_workers
93
101
  serial_scan_options = @options.reject { |key| %i[device devices].include?(key) }
94
102
  serial_scan_options[:destination] ||= Scan&.config&.fetch(:destination)
@@ -125,6 +133,7 @@ module TestCenter
125
133
  busy_workers.map(&:pid).each do |pid|
126
134
  Process.wait(pid)
127
135
  end
136
+ shutdown_cloned_simulators(@clones)
128
137
  busy_workers.each { |w| w.process_results }
129
138
  end
130
139
  end
@@ -3,34 +3,154 @@ module TestCenter
3
3
  require 'fastlane_core/ui/ui.rb'
4
4
  require 'fastlane/actions/scan'
5
5
  require 'plist'
6
+ require 'set'
6
7
 
7
8
  class TestCollector
8
9
  attr_reader :xctestrun_path
9
- attr_reader :only_testing
10
+ attr_reader :batches
11
+ attr_reader :testables
10
12
 
11
13
  def initialize(options)
12
- unless options[:xctestrun] || options[:derived_data_path]
13
- options[:derived_data_path] = default_derived_data_path(options)
14
+ @invocation_based_tests = options[:invocation_based_tests]
15
+ @swift_test_prefix = options[:swift_test_prefix]
16
+
17
+ @xctestrun_path = self.class.xctestrun_filepath(options)
18
+ initialize_batches(options)
19
+ end
20
+
21
+ private
22
+
23
+ def initialize_batches(options)
24
+ if options[:batches]
25
+ expand_given_batches_to_full_test_identifiers(options)
26
+ else
27
+ derive_batches_from_tests(options)
14
28
  end
15
- @xctestrun_path = options[:xctestrun] || derived_testrun_path(options[:derived_data_path], options[:scheme])
16
- unless @xctestrun_path && File.exist?(@xctestrun_path)
17
- FastlaneCore::UI.user_error!("Error: cannot find xctestrun file '#{@xctestrun_path}'")
29
+ end
30
+
31
+ def expand_given_batches_to_full_test_identifiers(options)
32
+ @batches = options[:batches]
33
+ testables = Set.new
34
+ @batches.each do |batch|
35
+ expand_test_identifiers(batch)
36
+ batch.each { |t| testables << t.split('/')[0] }
18
37
  end
19
- @only_testing = options[:only_testing] || only_testing_from_testplan(options)
20
- if @only_testing.kind_of?(String)
21
- @only_testing = @only_testing.split(',')
38
+ @testables = testables.to_a
39
+ end
40
+
41
+ def derive_batch_count(options)
42
+ batch_count = options.fetch(:batch_count, 1)
43
+ if batch_count == 1 && options.fetch(:parallel_testrun_count, 0) > 1
44
+ # if the batch count is 1, and the users wants parallel runs
45
+ # we *must* set the batch count to the same number of parallel
46
+ # runs or else the desired reports will not be written
47
+ batch_count = options[:parallel_testrun_count]
22
48
  end
23
- @skip_testing = options[:skip_testing]
24
- @invocation_based_tests = options[:invocation_based_tests]
25
- @batch_count = options[:batch_count]
26
- if @batch_count == 1 && options[:parallel_testrun_count] > 1
27
- @batch_count = options[:parallel_testrun_count]
49
+ batch_count
50
+ end
51
+
52
+ def derive_only_testing(options)
53
+ only_testing = options[:only_testing] || self.class.only_testing_from_testplan(options)
54
+ if only_testing && only_testing.kind_of?(String)
55
+ only_testing = only_testing.split(',').map(&:strip)
28
56
  end
57
+ only_testing
58
+ end
29
59
 
30
- @swift_test_prefix = options[:swift_test_prefix]
60
+ def testable_tests_hash_from_options(options)
61
+ testable_tests_hash = Hash.new { |h, k| h[k] = [] }
62
+ only_testing = derive_only_testing(options)
63
+ if only_testing
64
+ expand_test_identifiers(only_testing)
65
+ only_testing.each do |test_identifier|
66
+ testable = test_identifier.split('/')[0]
67
+ testable_tests_hash[testable] << test_identifier
68
+ end
69
+ else
70
+ testable_tests_hash = xctestrun_known_tests.clone
71
+ if options[:skip_testing]
72
+ expand_test_identifiers(options[:skip_testing])
73
+ testable_tests_hash.each do |testable, test_identifiers|
74
+ test_identifiers.replace(test_identifiers - options[:skip_testing])
75
+ testable_tests_hash.delete(testable) if test_identifiers.empty?
76
+ end
77
+ end
78
+ end
79
+ testable_tests_hash
80
+ end
81
+
82
+ def derive_batches_from_tests(options)
83
+ @batches = []
84
+ testable_tests_hash = testable_tests_hash_from_options(options)
85
+ @testables = testable_tests_hash.keys
86
+ batch_count = derive_batch_count(options)
87
+ testable_tests_hash.each do |testable, test_identifiers|
88
+ next if test_identifiers.empty?
89
+
90
+ if batch_count > 1
91
+ slice_count = [(test_identifiers.length / batch_count.to_f).ceil, 1].max
92
+ test_identifiers.each_slice(slice_count).to_a.each do |batch|
93
+ @batches << batch
94
+ end
95
+ else
96
+ @batches << test_identifiers
97
+ end
98
+ end
99
+ end
100
+
101
+ def expand_test_identifiers(test_identifiers)
102
+ all_known_tests = nil
103
+ test_identifiers.each_with_index do |test_identifier, index|
104
+ test_components = test_identifier.split('/')
105
+ is_full_test_identifier = (test_components.size == 3)
106
+ next if is_full_test_identifier
107
+
108
+ all_known_tests ||= xctestrun_known_tests.clone
109
+
110
+ testsuite = ''
111
+ testable = test_components[0]
112
+ expanded_test_identifiers = []
113
+ if test_components.size == 1
114
+ # this is a testable, also known as a test target. Let's expand it out
115
+ # to all of its tests. Note: a test target can have many testSuites, each
116
+ # with their own testCases.
117
+ if all_known_tests[testable].to_a.empty?
118
+ FastlaneCore::UI.verbose("Unable to expand #{testable} to constituent tests")
119
+ expanded_test_identifiers = [testable]
120
+ else
121
+ expanded_test_identifiers = all_known_tests[testable]
122
+ end
123
+ else
124
+ # this is a testable and a test suite, let's expand it out to all of
125
+ # its testCases. Note: if the user put the same test identifier in more than
126
+ # one place in this array, this could lead to multiple repititions of the same
127
+ # set of test identifiers
128
+ testsuite = test_components[1]
129
+ expanded_test_identifiers = all_known_tests[testable].select do |known_test|
130
+ known_test.split('/')[1] == testsuite
131
+ end
132
+ end
133
+ test_identifiers.delete_at(index)
134
+ test_identifiers.insert(index, *expanded_test_identifiers)
135
+ end
136
+ end
137
+
138
+ def xctestrun_known_tests
139
+ unless @known_tests
140
+ config = FastlaneCore::Configuration.create(
141
+ ::Fastlane::Actions::TestsFromXctestrunAction.available_options,
142
+ {
143
+ xctestrun: @xctestrun_path,
144
+ invocation_based_tests: @invocation_based_tests,
145
+ swift_test_prefix: @swift_test_prefix
146
+ }
147
+ )
148
+ @known_tests = ::Fastlane::Actions::TestsFromXctestrunAction.run(config)
149
+ end
150
+ @known_tests
31
151
  end
32
152
 
33
- def only_testing_from_testplan(options)
153
+ def self.only_testing_from_testplan(options)
34
154
  return unless options[:testplan] && options[:scheme]
35
155
 
36
156
  config = FastlaneCore::Configuration.create(
@@ -60,153 +180,26 @@ module TestCenter
60
180
  return test_options[:only_testing]
61
181
  end
62
182
 
63
- def default_derived_data_path(options)
183
+ def self.default_derived_data_path
64
184
  project_derived_data_path = Scan.project.build_settings(key: "BUILT_PRODUCTS_DIR")
65
185
  File.expand_path("../../..", project_derived_data_path)
66
186
  end
67
187
 
68
- def derived_testrun_path(derived_data_path, scheme)
188
+ def self.derived_testrun_path(derived_data_path)
69
189
  xctestrun_files = Dir.glob("#{derived_data_path}/Build/Products/*.xctestrun")
70
190
  xctestrun_files.sort { |f1, f2| File.mtime(f1) <=> File.mtime(f2) }.last
71
191
  end
72
192
 
73
- def testables
74
- unless @testables
75
- if @only_testing
76
- @testables ||= only_testing_to_testables_tests.keys
77
- else
78
- @testables = xctestrun_known_tests.keys
79
- end
80
- end
81
- @testables
82
- end
83
-
84
- def only_testing_to_testables_tests
85
- tests = Hash.new { |h, k| h[k] = [] }
86
- @only_testing.sort.each do |test_identifier|
87
- testable = test_identifier.split('/', 2)[0]
88
- tests[testable] << test_identifier
89
- end
90
- tests
91
- end
92
-
93
- def xctestrun_known_tests
94
- unless @known_tests
95
- config = FastlaneCore::Configuration.create(
96
- ::Fastlane::Actions::TestsFromXctestrunAction.available_options,
97
- {
98
- xctestrun: @xctestrun_path,
99
- invocation_based_tests: @invocation_based_tests,
100
- swift_test_prefix: @swift_test_prefix
101
- }
102
- )
103
- @known_tests = ::Fastlane::Actions::TestsFromXctestrunAction.run(config)
104
- end
105
- @known_tests
106
- end
107
-
108
- def expand_short_testidentifiers_to_tests(testables_tests)
109
- # Remember, testable_tests is of the format:
110
- # {
111
- # 'testable1' => [
112
- # 'testsuite1/testcase1',
113
- # 'testsuite1/testcase2',
114
- # 'testsuite2/testcase1',
115
- # 'testsuite2/testcase2',
116
- # ...
117
- # 'testsuiteN/testcase1', ... 'testsuiteN/testcaseM'
118
- # ],
119
- # ...
120
- # 'testableO' => [
121
- # 'testsuite1/testcase1',
122
- # 'testsuite1/testcase2',
123
- # 'testsuite2/testcase1',
124
- # 'testsuite2/testcase2',
125
- # ...
126
- # 'testsuiteN/testcase1', ... 'testsuiteN/testcaseM'
127
- # ]
128
- # }
129
- return if @invocation_based_tests
130
-
131
- # iterate among all the test identifers for each testable
132
- # A test identifier is seperated into components by '/'
133
- # if a test identifier has only 2 separators, it probably is
134
- # 'testable/testsuite' (but it could be 'testsuite/testcase' )
135
- all_known_tests = nil
136
- known_tests = []
137
- testables_tests.each do |testable, tests|
138
- tests.each_with_index do |test, index|
139
- test_components = test.split('/')
140
- is_full_test_identifier = (test_components.size == 3)
141
- next if is_full_test_identifier
142
-
143
- all_known_tests ||= xctestrun_known_tests.clone
144
-
145
- testsuite = ''
146
- if test_components.size == 1
147
- if test_components[0] == testable
148
- # The following || [] is just in case the user provided multiple
149
- # test targets or there are no actual tests found.
150
- testables_tests[testable][index] = all_known_tests[testable] || []
151
- all_known_tests.delete(testable)
152
- next
153
- end
154
-
155
- testsuite = test_components[0]
156
- else
157
- testsuite = test_components[1]
158
- end
159
-
160
- testables_tests[testable][index], all_known_tests[testable] = all_known_tests[testable].partition do |known_test|
161
- known_test.split('/')[1] == testsuite
162
- end
163
- end
164
- testables_tests[testable].flatten!
165
- end
166
- end
167
-
168
- def testables_tests
169
- unless @testables_tests
170
- if @only_testing
171
- @testables_tests = only_testing_to_testables_tests
172
- expand_short_testidentifiers_to_tests(@testables_tests)
173
- else
174
- @testables_tests = xctestrun_known_tests
175
- if @skip_testing
176
- skipped_testable_tests = Hash.new { |h, k| h[k] = [] }
177
- @skip_testing.sort.each do |skipped_test_identifier|
178
- testable = skipped_test_identifier.split('/', 2)[0]
179
- skipped_testable_tests[testable] << skipped_test_identifier
180
- end
181
- @testables_tests.each_key do |testable|
182
- @testables_tests[testable] -= skipped_testable_tests[testable]
183
- end
184
- end
185
- end
193
+ def self.xctestrun_filepath(options)
194
+ unless options[:xctestrun] || options[:derived_data_path]
195
+ options[:derived_data_path] = default_derived_data_path
186
196
  end
197
+ path = (options[:xctestrun] || derived_testrun_path(options[:derived_data_path]))
187
198
 
188
- @testables_tests
189
- end
190
-
191
- def test_batches
192
- if @batches.nil?
193
- @batches = []
194
- testables.each do |testable|
195
- testable_tests = testables_tests[testable]
196
- next if testable_tests.empty?
197
-
198
- if @batch_count > 1
199
- slice_count = [(testable_tests.length / @batch_count.to_f).ceil, 1].max
200
- testable_tests.each_slice(slice_count).to_a.each do |tests_batch|
201
- @batches << tests_batch
202
- end
203
- else
204
- @batches << testable_tests
205
- end
206
- end
199
+ unless path && File.exist?(path)
200
+ FastlaneCore::UI.user_error!("Error: cannot find xctestrun file '#{path}'")
207
201
  end
208
-
209
- @batches
202
+ path
210
203
  end
211
204
  end
212
205
  end
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module TestCenter
3
- VERSION = "3.14.3"
3
+ VERSION = "3.14.8"
4
4
  end
5
5
  end
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.14.3
4
+ version: 3.14.8
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-09-15 00:00:00.000000000 Z
11
+ date: 2020-11-13 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: []