fastlane-plugin-test_center 3.13.1 → 3.14.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 79e2765eea8d10df4efd2039d298d2e7b3e9481844686eb5dc5c3e5223f96171
4
- data.tar.gz: a5856750d9519fe3db99c10aebe9b320a91aa6285a15919ea0547069fdc6627b
3
+ metadata.gz: 4364b9e3e49e3aa1a3bbd1e26feb07e449de72900bcd1ec3341ff8d1b0ca62ad
4
+ data.tar.gz: f983745b33c34838839d546aff4c4e407673c8241e72ab14b695d288792bdac4
5
5
  SHA512:
6
- metadata.gz: 7d40544a3352ddd61af2c9ff77d842d006a0400df54bb9e2492ccd0f01f7ab514a008d6a82c4f1f2fc979e840a599cd1e0e4d514afd9d053e5ad6473347d2f5d
7
- data.tar.gz: cbd5f866464960e4a35b3b3f59baceafc6621eb7d0917f72d795cb53973f62f342dd717587431f8f72da958164b9f3b2ed8c709f3661dd759584b0c4574c65ce
6
+ metadata.gz: 1bb52a9bcc82781df58b2ab2b6dd33c1831da0b345b9b51842959b8a372e5341b773cf639e3cb884bccc426336dad01c8ff1e7fc418fad0baefe201e12c46bab
7
+ data.tar.gz: 8d6e6a9321e13aada993f1099f45b7317f4d4c30e8bbdfa55a35a49a1a0dd3aa6c7e06c7dd244132e37d4c149ac0a6b9f94d9198d3f0bd8ee912853fa44f773d
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
 
@@ -44,9 +44,10 @@ module Fastlane
44
44
  html_file_contents = File.read(html_report_filepath)
45
45
  File.open(html_report_filepath, 'w') do |file|
46
46
  html_file_contents.each_line do |line|
47
- m = %r{(<section class="test-detail[^"]*">)(.*(<|>).*)(</section>)}.match(line)
47
+ m = %r{(<section class="test-detail[^"]*">)(.*(<|>|&(?!amp;)).*)(</section>)}.match(line)
48
48
  if m
49
49
  test_details = m[2]
50
+ test_details.gsub!(/&(?!amp;)/, '&amp;')
50
51
  test_details.gsub!('<', '&lt;')
51
52
  test_details.gsub!('>', '&gt;')
52
53
  line = m[1] + test_details + m[4]
@@ -1,6 +1,7 @@
1
1
  module Fastlane
2
2
  module Actions
3
3
  class CollateJunitReportsAction < Action
4
+ require 'set'
4
5
 
5
6
  def self.run(params)
6
7
  report_filepaths = params[:reports]
@@ -9,17 +10,27 @@ module Fastlane
9
10
  else
10
11
  UI.verbose("collate_junit_reports with #{report_filepaths}")
11
12
  reports = report_filepaths.map { |report_filepath| REXML::Document.new(File.new(report_filepath)) }
13
+ packages = reports.map { |r| r.root.attribute('name').value }.uniq
14
+ combine_multiple_targets = packages.size > 1
15
+
12
16
  # copy any missing testsuites
13
17
  target_report = reports.shift
14
- preprocess_testsuites(target_report)
18
+
19
+ package = target_report.root.attribute('name').value
20
+ preprocess_testsuites(target_report, package, combine_multiple_targets)
15
21
 
16
22
  reports.each do |report|
17
23
  increment_testable_tries(target_report.root, report.root)
18
- preprocess_testsuites(report)
24
+ package = report.root.attribute('name').value
25
+ preprocess_testsuites(report, package, combine_multiple_targets)
19
26
  UI.verbose("> collating last report file #{report_filepaths.last}")
20
27
  report.elements.each('//testsuite') do |testsuite|
21
28
  testsuite_name = testsuite.attribute('name').value
22
- target_testsuite = REXML::XPath.first(target_report, "//testsuite[@name='#{testsuite_name}']")
29
+ package_attribute = ''
30
+ if combine_multiple_targets
31
+ package_attribute = "@package='#{package}'"
32
+ end
33
+ target_testsuite = REXML::XPath.first(target_report, "//testsuite[@name='#{testsuite_name}' #{package_attribute}]")
23
34
  if target_testsuite
24
35
  UI.verbose(" > collating testsuite #{testsuite_name}")
25
36
  collate_testsuite(target_testsuite, testsuite)
@@ -36,6 +47,7 @@ module Fastlane
36
47
  end
37
48
  testable = REXML::XPath.first(target_report, 'testsuites')
38
49
  update_testable_counts(testable)
50
+ testable.add_attribute('name', packages.to_a.join(', '))
39
51
 
40
52
  FileUtils.mkdir_p(File.dirname(params[:collated_report]))
41
53
  File.open(params[:collated_report], 'w') do |f|
@@ -81,10 +93,11 @@ module Fastlane
81
93
  update_testsuite_counts(testsuite)
82
94
  end
83
95
 
84
- def self.preprocess_testsuites(report)
96
+ def self.preprocess_testsuites(report, package, combine_multiple_targets)
85
97
  report.elements.each('//testsuite') do |testsuite|
86
98
  flatten_duplicate_testsuites(report, testsuite)
87
99
  collapse_testcase_multiple_failures_in_testsuite(testsuite)
100
+ testsuite.add_attribute('package', package) if combine_multiple_targets
88
101
  end
89
102
  end
90
103
 
@@ -221,10 +221,12 @@ module Fastlane
221
221
  'collate the test_result bundles to a temporary bundle \"result.test_result\"'
222
222
  )
223
223
  bundles = Dir['../spec/fixtures/*.test_result'].map { |relpath| File.absolute_path(relpath) }
224
- collate_test_result_bundles(
225
- bundles: bundles,
226
- collated_bundle: File.join('test_output', 'result.test_result')
227
- )
224
+ Dir.mktmpdir('test_output') do |dir|
225
+ collate_test_result_bundles(
226
+ bundles: bundles,
227
+ collated_bundle: File.join(dir, 'result.test_result')
228
+ )
229
+ end
228
230
  "
229
231
  ]
230
232
  end
@@ -86,15 +86,19 @@ module Fastlane
86
86
  def self.example_code
87
87
  [
88
88
  "
89
+ require 'tmpdir'
90
+
89
91
  UI.important(
90
92
  'example: ' \\
91
93
  'collate the xcresult bundles to a temporary xcresult bundle \"result.xcresult\"'
92
94
  )
93
- xcresults = Dir['../spec/fixtures/*.xcresult'].map { |relpath| File.absolute_path(relpath) }
94
- collate_xcresults(
95
- xcresults: xcresults,
96
- collated_xcresult: File.join('test_output', 'result.xcresult')
97
- )
95
+ xcresults = Dir['../spec/fixtures/AtomicBoyUITests-batch-{3,4}/result.xcresult'].map { |relpath| File.absolute_path(relpath) }
96
+ Dir.mktmpdir('test_output') do |dir|
97
+ collate_xcresults(
98
+ xcresults: xcresults,
99
+ collated_xcresult: File.join(dir, 'result.xcresult')
100
+ )
101
+ end
98
102
  "
99
103
  ]
100
104
  end
@@ -171,7 +171,10 @@ module Fastlane
171
171
  use_scanfile_to_override_settings(scan_options)
172
172
  turn_off_concurrent_workers(scan_options)
173
173
  UI.important("Turning off :skip_build as it doesn't do anything with multi_scan") if scan_options[:skip_build]
174
- scan_options.reject! { |k,v| k == :skip_build }
174
+ if scan_options[:disable_xcpretty]
175
+ UI.important("Turning off :disable_xcpretty as xcpretty is needed to generate junit reports for retrying failed tests")
176
+ end
177
+ scan_options.reject! { |k,v| %i[skip_build disable_xcpretty].include?(k) }
175
178
  ScanHelper.remove_preexisting_simulator_logs(scan_options)
176
179
  if scan_options[:test_without_building]
177
180
  UI.verbose("Preparing Scan config options for multi_scan testing")
@@ -358,6 +361,14 @@ module Fastlane
358
361
  UI.user_error!("Error: Batch counts must be greater than zero") unless count > 0
359
362
  end
360
363
  ),
364
+ FastlaneCore::ConfigItem.new(
365
+ key: :batches,
366
+ env_name: "FL_MULTI_SCAN_BATCHES",
367
+ 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",
368
+ type: Array,
369
+ optional: true,
370
+ conflicting_options: [:batch_count]
371
+ ),
361
372
  FastlaneCore::ConfigItem.new(
362
373
  key: :retry_test_runner_failures,
363
374
  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'",
@@ -372,7 +383,7 @@ module Fastlane
372
383
  is_string: false,
373
384
  default_value: false,
374
385
  optional: true,
375
- conflicting_options: [:batch_count],
386
+ conflicting_options: %i[batch_count batches],
376
387
  conflict_block: proc do |value|
377
388
  UI.user_error!(
378
389
  "Error: Can't use 'invocation_based_tests' and 'batch_count' options in one run, "\
@@ -430,7 +441,7 @@ module Fastlane
430
441
  ),
431
442
  FastlaneCore::ConfigItem.new(
432
443
  key: :testrun_completed_block,
433
- description: 'A block invoked each time a test run completes. When combined with :parallel_testrun_count, will be called separately in each child process',
444
+ 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',
434
445
  optional: true,
435
446
  is_string: false,
436
447
  default_value: nil,
@@ -458,6 +469,10 @@ module Fastlane
458
469
 
459
470
  # UI.abort_with_message!('You could conditionally abort')
460
471
  UI.message(\"\\\u1F60A everything is fine, let's continue try \#{try_attempt + 1} for batch \#{batch}\")
472
+ {
473
+ continue: true,
474
+ only_testing: ['AtomicBoyUITests/AtomicBoyUITests/testExample17']
475
+ }
461
476
  end
462
477
 
463
478
  multi_scan(
@@ -483,6 +498,199 @@ module Fastlane
483
498
  output_files: 'report.json',
484
499
  fail_build: false
485
500
  )
501
+ ",
502
+ "
503
+ UI.header('batches feature')
504
+ multi_scan(
505
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
506
+ scheme: 'AtomicBoy',
507
+ try_count: 3,
508
+ fail_build: false,
509
+ batches: [
510
+ [
511
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample5',
512
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample10',
513
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample15'
514
+ ],
515
+ [
516
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample6',
517
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample12',
518
+ 'AtomicBoyUITests/AtomicBoyUITests/testExample18'
519
+ ]
520
+ ]
521
+ )
522
+ "
523
+ ]
524
+ end
525
+
526
+ def self.integration_tests
527
+ [
528
+ "
529
+ UI.header('Basic test')
530
+ multi_scan(
531
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
532
+ scheme: 'AtomicBoy',
533
+ fail_build: false,
534
+ try_count: 2,
535
+ disable_xcpretty: true
536
+ )
537
+ ",
538
+ "
539
+ UI.header('Basic test with 1 specific test')
540
+ multi_scan(
541
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
542
+ scheme: 'AtomicBoy',
543
+ fail_build: false,
544
+ try_count: 2,
545
+ only_testing: ['AtomicBoyUITests/AtomicBoyUITests/testExample']
546
+ )
547
+ ",
548
+ "
549
+ UI.header('Basic test with test target expansion')
550
+ multi_scan(
551
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
552
+ scheme: 'AtomicBoy',
553
+ fail_build: false,
554
+ try_count: 2,
555
+ only_testing: ['AtomicBoyUITests', 'AtomicBoyTests']
556
+ )
557
+ ",
558
+ "
559
+ UI.header('Parallel test run')
560
+ multi_scan(
561
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
562
+ scheme: 'AtomicBoy',
563
+ fail_build: false,
564
+ try_count: 2,
565
+ parallel_testrun_count: 2
566
+ )
567
+ ",
568
+ "
569
+ UI.header('Parallel test run with fewer tests than parallel test runs')
570
+ multi_scan(
571
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
572
+ scheme: 'AtomicBoy',
573
+ fail_build: false,
574
+ try_count: 2,
575
+ parallel_testrun_count: 4,
576
+ only_testing: ['AtomicBoyUITests/AtomicBoyUITests/testExample']
577
+ )
578
+ ",
579
+ "
580
+ UI.header('Basic test with batch count')
581
+ multi_scan(
582
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
583
+ scheme: 'AtomicBoy',
584
+ fail_build: false,
585
+ try_count: 2,
586
+ batch_count: 2
587
+ )
588
+ ",
589
+ "
590
+ UI.header('Basic test with batches')
591
+ multi_scan(
592
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
593
+ scheme: 'AtomicBoy',
594
+ fail_build: false,
595
+ try_count: 2,
596
+ batches: [
597
+ ['AtomicBoyUITests/AtomicBoyUITests/testExample', 'AtomicBoyUITests/AtomicBoyUITests/testExample2'],
598
+ ['AtomicBoyUITests/AtomicBoyUITests/testExample3', 'AtomicBoyUITests/AtomicBoyUITests/testExample4']
599
+ ],
600
+ parallel_testrun_count: 2
601
+ )
602
+ ",
603
+ "
604
+ UI.header('Basic test with xcresult')
605
+ multi_scan(
606
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
607
+ scheme: 'AtomicBoy',
608
+ output_types: 'xcresult',
609
+ output_files: 'result.xcresult',
610
+ collate_reports: false,
611
+ fail_build: false,
612
+ try_count: 2,
613
+ batch_count: 2
614
+ )
615
+ "
616
+ ]
617
+ end
618
+
619
+ def self.integration_tests
620
+ [
621
+ "
622
+ UI.header('Basic test')
623
+ multi_scan(
624
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
625
+ scheme: 'AtomicBoy',
626
+ fail_build: false,
627
+ try_count: 2,
628
+ disable_xcpretty: true
629
+ )
630
+ ",
631
+ "
632
+ UI.header('Basic test with 1 specific test')
633
+ multi_scan(
634
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
635
+ scheme: 'AtomicBoy',
636
+ fail_build: false,
637
+ try_count: 2,
638
+ only_testing: ['AtomicBoyUITests/AtomicBoyUITests/testExample']
639
+ )
640
+ ",
641
+ "
642
+ UI.header('Basic test with test target expansion')
643
+ multi_scan(
644
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
645
+ scheme: 'AtomicBoy',
646
+ fail_build: false,
647
+ try_count: 2,
648
+ only_testing: ['AtomicBoyUITests', 'AtomicBoyTests']
649
+ )
650
+ ",
651
+ "
652
+ UI.header('Parallel test run')
653
+ multi_scan(
654
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
655
+ scheme: 'AtomicBoy',
656
+ fail_build: false,
657
+ try_count: 2,
658
+ parallel_testrun_count: 2
659
+ )
660
+ ",
661
+ "
662
+ UI.header('Parallel test run with fewer tests than parallel test runs')
663
+ multi_scan(
664
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
665
+ scheme: 'AtomicBoy',
666
+ fail_build: false,
667
+ try_count: 2,
668
+ parallel_testrun_count: 4,
669
+ only_testing: ['AtomicBoyUITests/AtomicBoyUITests/testExample']
670
+ )
671
+ ",
672
+ "
673
+ UI.header('Basic test with batches')
674
+ multi_scan(
675
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
676
+ scheme: 'AtomicBoy',
677
+ fail_build: false,
678
+ try_count: 2,
679
+ batch_count: 2
680
+ )
681
+ ",
682
+ "
683
+ UI.header('Basic test with xcresult')
684
+ multi_scan(
685
+ workspace: File.absolute_path('../AtomicBoy/AtomicBoy.xcworkspace'),
686
+ scheme: 'AtomicBoy',
687
+ output_types: 'xcresult',
688
+ output_files: 'result.xcresult',
689
+ collate_reports: false,
690
+ fail_build: false,
691
+ try_count: 2,
692
+ batch_count: 2
693
+ )
486
694
  "
487
695
  ]
488
696
  end
@@ -0,0 +1,103 @@
1
+ require 'trainer'
2
+ require 'shellwords'
3
+
4
+ module Fastlane
5
+ module Actions
6
+ class TestsFromXcresultAction < Action
7
+ def self.run(params)
8
+ unless FastlaneCore::Helper.xcode_at_least?('11.0.0')
9
+ UI.error("Error: tests_from_xcresult requires at least Xcode 11.0")
10
+ return {}
11
+ end
12
+
13
+ xcresult_path = File.absolute_path(params[:xcresult])
14
+
15
+ # taken from the rubygem trainer, in the test_parser.rb module
16
+ result_bundle_object_raw = sh("xcrun xcresulttool get --path #{xcresult_path.shellescape} --format json", print_command: false, print_command_output: false)
17
+ result_bundle_object = JSON.parse(result_bundle_object_raw)
18
+
19
+ # Parses JSON into ActionsInvocationRecord to find a list of all ids for ActionTestPlanRunSummaries
20
+ actions_invocation_record = Trainer::XCResult::ActionsInvocationRecord.new(result_bundle_object)
21
+ test_refs = actions_invocation_record.actions.map do |action|
22
+ action.action_result.tests_ref
23
+ end.compact
24
+
25
+ ids = test_refs.map(&:id)
26
+ summaries = ids.map do |id|
27
+ raw = sh("xcrun xcresulttool get --format json --path #{xcresult_path.shellescape} --id #{id}", print_command: false, print_command_output: false)
28
+ json = JSON.parse(raw)
29
+ Trainer::XCResult::ActionTestPlanRunSummaries.new(json)
30
+ end
31
+ failures = actions_invocation_record.issues.test_failure_summaries || []
32
+ all_summaries = summaries.map(&:summaries).flatten
33
+ testable_summaries = all_summaries.map(&:testable_summaries).flatten
34
+ failed = []
35
+ passing = []
36
+ failure_details = {}
37
+ rows = testable_summaries.map do |testable_summary|
38
+ all_tests = testable_summary.all_tests.flatten
39
+ all_tests.each do |t|
40
+ if t.test_status == 'Success'
41
+ passing << "#{t.parent.name}/#{t.identifier}"
42
+ else
43
+ test_identifier = "#{t.parent.name}/#{t.identifier}"
44
+ failed << test_identifier
45
+ failure = t.find_failure(failures)
46
+ if failure
47
+ failure_details[test_identifier] = {
48
+ message: failure.failure_message
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ {
55
+ failed: failed.uniq,
56
+ passing: passing.uniq,
57
+ failure_details: failure_details
58
+ }
59
+ end
60
+
61
+ #####################################################
62
+ # @!group Documentation
63
+ #####################################################
64
+
65
+ def self.description
66
+ "☑️ Retrieves the failing and passing tests as reportedn an xcresult bundle"
67
+ end
68
+
69
+
70
+ def self.available_options
71
+ [
72
+ FastlaneCore::ConfigItem.new(
73
+ key: :xcresult,
74
+ env_name: "FL_TESTS_FROM_XCRESULT_XCRESULT_PATH",
75
+ description: "The path to the xcresult bundle to retrieve the tests from",
76
+ verify_block: proc do |path|
77
+ UI.user_error!("Error: cannot find the xcresult bundle at '#{path}'") unless Dir.exist?(path)
78
+ UI.user_error!("Error: cannot parse files that are not in the xcresult format") unless File.extname(path) == ".xcresult"
79
+ end
80
+ )
81
+ ]
82
+ end
83
+
84
+ def self.return_value
85
+ "A Hash with information about the test results:\r\n" \
86
+ "failed: an Array of the failed test identifiers\r\n" \
87
+ "passing: an Array of the passing test identifiers\r\n"
88
+ end
89
+
90
+ def self.authors
91
+ ["lyndsey-ferguson/lyndseydf"]
92
+ end
93
+
94
+ def self.category
95
+ :testing
96
+ end
97
+
98
+ def self.is_supported?(platform)
99
+ %i[ios mac].include?(platform)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -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,
@@ -65,10 +65,11 @@ module TestCenter
65
65
 
66
66
  @test_collector = TestCollector.new(@options)
67
67
  @options.reject! { |key| %i[testplan].include?(key) }
68
- @batch_count = @test_collector.test_batches.size
69
- if @test_collector.test_batches.flatten.size < @options[:parallel_testrun_count].to_i
70
- FastlaneCore::UI.important(":parallel_testrun_count greater than the number of tests (#{@test_collector.test_batches.flatten.size}). Reducing to that number.")
71
- @options[:parallel_testrun_count] = @test_collector.test_batches.flatten.size
68
+ @batch_count = @test_collector.batches.size
69
+ tests = @test_collector.batches.flatten
70
+ if tests.size < @options[:parallel_testrun_count].to_i
71
+ FastlaneCore::UI.important(":parallel_testrun_count greater than the number of tests (#{tests.size}). Reducing to that number.")
72
+ @options[:parallel_testrun_count] = tests.size
72
73
  end
73
74
  end
74
75
 
@@ -195,7 +196,7 @@ module TestCenter
195
196
  pool = TestBatchWorkerPool.new(pool_options)
196
197
  pool.setup_workers
197
198
 
198
- remaining_test_batches = @test_collector.test_batches.clone
199
+ remaining_test_batches = @test_collector.batches.clone
199
200
  remaining_test_batches.each_with_index do |test_batch, current_batch_index|
200
201
  worker = pool.wait_for_worker
201
202
  FastlaneCore::UI.message("Starting test run #{current_batch_index + 1}")
@@ -208,7 +209,7 @@ module TestCenter
208
209
  end
209
210
 
210
211
  def scan_options_for_worker(test_batch, batch_index)
211
- if @test_collector.test_batches.size > 1
212
+ if @test_collector.batches.size > 1
212
213
  # If there are more than 1 batch, then we want each batch result
213
214
  # sent to a "batch index" output folder to be collated later
214
215
  # into the requested output_folder.
@@ -233,6 +234,7 @@ module TestCenter
233
234
  @test_collector.testables.each do |testable|
234
235
  collate_batched_reports_for_testable(testable)
235
236
  end
237
+ collate_multitarget_junits
236
238
  move_single_testable_reports_to_final_location
237
239
  end
238
240
 
@@ -294,6 +296,31 @@ module TestCenter
294
296
  File.symlink(xcresult_bundlename_path, test_result_bundlename_path)
295
297
  end
296
298
 
299
+ def collate_multitarget_junits
300
+ return if @test_collector.testables.size < 2
301
+
302
+ Fastlane::UI.verbose("Collating test targets's junit results")
303
+
304
+ given_custom_report_file_name = @options[:custom_report_file_name]
305
+ given_output_types = @options[:output_types]
306
+ given_output_files = @options[:output_files]
307
+
308
+ report_name_helper = ReportNameHelper.new(
309
+ given_output_types,
310
+ given_output_files,
311
+ given_custom_report_file_name
312
+ )
313
+
314
+ absolute_output_directory = File.absolute_path(output_directory)
315
+ source_reports_directory_glob = "#{absolute_output_directory}/*"
316
+
317
+ TestCenter::Helper::MultiScanManager::ReportCollator.new(
318
+ source_reports_directory_glob: source_reports_directory_glob,
319
+ output_directory: absolute_output_directory,
320
+ reportnamer: report_name_helper
321
+ ).collate_junit_reports
322
+ end
323
+
297
324
  def collate_batched_reports_for_testable(testable)
298
325
  FastlaneCore::UI.verbose("Collating results for all batches")
299
326
 
@@ -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
+ expand_test_identifiers = [testable]
120
+ else
121
+ expand_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, *expand_test_identifiers)
135
+ end
31
136
  end
32
137
 
33
- def only_testing_from_testplan(options)
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
151
+ end
152
+
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,107 +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
- config = FastlaneCore::Configuration.create(
95
- ::Fastlane::Actions::TestsFromXctestrunAction.available_options,
96
- {
97
- xctestrun: @xctestrun_path,
98
- invocation_based_tests: @invocation_based_tests,
99
- swift_test_prefix: @swift_test_prefix
100
- }
101
- )
102
- ::Fastlane::Actions::TestsFromXctestrunAction.run(config)
103
- end
104
-
105
- def expand_testsuites_to_tests
106
- return if @invocation_based_tests
107
-
108
- known_tests = []
109
- @testables_tests.each do |testable, tests|
110
- tests.each_with_index do |test, index|
111
- if test.count('/') < 2
112
- known_tests += xctestrun_known_tests[testable]
113
- test_components = test.split('/')
114
- testsuite = test_components.size == 1 ? test_components[0] : test_components[1]
115
- @testables_tests[testable][index] = known_tests.select { |known_test| known_test.include?(testsuite) }
116
- end
117
- end
118
- @testables_tests[testable].flatten!
119
- end
120
- end
121
-
122
- def testables_tests
123
- unless @testables_tests
124
- if @only_testing
125
- @testables_tests = only_testing_to_testables_tests
126
- expand_testsuites_to_tests
127
- else
128
- @testables_tests = xctestrun_known_tests
129
- if @skip_testing
130
- skipped_testable_tests = Hash.new { |h, k| h[k] = [] }
131
- @skip_testing.sort.each do |skipped_test_identifier|
132
- testable = skipped_test_identifier.split('/', 2)[0]
133
- skipped_testable_tests[testable] << skipped_test_identifier
134
- end
135
- @testables_tests.each_key do |testable|
136
- @testables_tests[testable] -= skipped_testable_tests[testable]
137
- end
138
- end
139
- end
193
+ def self.xctestrun_filepath(options)
194
+ unless options[:xctestrun] || options[:derived_data_path]
195
+ options[:derived_data_path] = default_derived_data_path
140
196
  end
197
+ path = (options[:xctestrun] || derived_testrun_path(options[:derived_data_path]))
141
198
 
142
- @testables_tests
143
- end
144
-
145
- def test_batches
146
- if @batches.nil?
147
- @batches = []
148
- testables.each do |testable|
149
- testable_tests = testables_tests[testable]
150
- next if testable_tests.empty?
151
-
152
- if @batch_count > 1
153
- slice_count = [(testable_tests.length / @batch_count.to_f).ceil, 1].max
154
- testable_tests.each_slice(slice_count).to_a.each do |tests_batch|
155
- @batches << tests_batch
156
- end
157
- else
158
- @batches << testable_tests
159
- end
160
- end
199
+ unless path && File.exist?(path)
200
+ FastlaneCore::UI.user_error!("Error: cannot find xctestrun file '#{path}'")
161
201
  end
162
-
163
- @batches
202
+ path
164
203
  end
165
204
  end
166
205
  end
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module TestCenter
3
- VERSION = "3.13.1"
3
+ VERSION = "3.14.4"
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.13.1
4
+ version: 3.14.4
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-08-03 00:00:00.000000000 Z
11
+ date: 2020-10-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: trainer
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: xcodeproj
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -270,6 +284,7 @@ files:
270
284
  - lib/fastlane/plugin/test_center/actions/test_options_from_testplan.rb
271
285
  - lib/fastlane/plugin/test_center/actions/testplans_from_scheme.rb
272
286
  - lib/fastlane/plugin/test_center/actions/tests_from_junit.rb
287
+ - lib/fastlane/plugin/test_center/actions/tests_from_xcresult.rb
273
288
  - lib/fastlane/plugin/test_center/actions/tests_from_xctestrun.rb
274
289
  - lib/fastlane/plugin/test_center/helper/fastlane_core/device_manager/simulator_extensions.rb
275
290
  - lib/fastlane/plugin/test_center/helper/html_test_report.rb
@@ -295,7 +310,7 @@ homepage: https://github.com/lyndsey-ferguson/fastlane-plugin-test_center
295
310
  licenses:
296
311
  - MIT
297
312
  metadata: {}
298
- post_install_message:
313
+ post_install_message:
299
314
  rdoc_options: []
300
315
  require_paths:
301
316
  - lib
@@ -310,8 +325,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
310
325
  - !ruby/object:Gem::Version
311
326
  version: '0'
312
327
  requirements: []
313
- rubygems_version: 3.0.3
314
- signing_key:
328
+ rubygems_version: 3.1.2
329
+ signing_key:
315
330
  specification_version: 4
316
331
  summary: Makes testing your iOS app easier
317
332
  test_files: []