fastlane-plugin-test_center 3.13.1 → 3.14.4

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: 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: []